blob: d5538f778023d2a703d8b7ad89e6f11239e07fa4 [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 Clark88ba9712018-04-12 14:00:24 -0400453 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400454 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
455 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
caryclark@google.com56f233a2012-11-19 13:06:06 +0000456 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400457 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400458 lineStart.set(0, 0);
Cary Clark48c464a2018-04-16 12:06:07 -0400459 signed char directions[] = {-1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000460 bool closedOrMoved = false;
Cary Clark8540e112018-04-11 14:30:27 -0400461 bool addedLine = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700463 bool insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400464 bool accumulatingRect = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000465 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000466 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700467 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
468 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000470 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700472 insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400473 accumulatingRect = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 case kLine_Verb: {
Cary Clark8540e112018-04-11 14:30:27 -0400475 if (accumulatingRect) {
476 lastPt = pts;
477 }
Cary Clark88ba9712018-04-12 14:00:24 -0400478 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
479 SkVector lineDelta = lineEnd - lineStart;
480 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 return false; // diagonal
482 }
Cary Clark88ba9712018-04-12 14:00:24 -0400483 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 break; // single point on side OK
485 }
Cary Clarkd4228472018-04-13 07:07:04 -0400486 addedLine = true;
Cary Clark48c464a2018-04-16 12:06:07 -0400487 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400489 directions[0] = nextDirection;
Cary Clark88ba9712018-04-12 14:00:24 -0400490 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 corners = 1;
492 closedOrMoved = false;
493 break;
494 }
495 if (closedOrMoved) {
496 return false; // closed followed by a line
497 }
Cary Clark48c464a2018-04-16 12:06:07 -0400498 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000499 break; // colinear with first
500 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000501 closedOrMoved = autoClose;
Cary Clark88ba9712018-04-12 14:00:24 -0400502 lineStart = lineEnd;
Cary Clark48c464a2018-04-16 12:06:07 -0400503 if (directions[corners - 1] == nextDirection) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 break; // colinear segment
505 }
Cary Clark48c464a2018-04-16 12:06:07 -0400506 if (corners >= 4) {
507 return false; // too many direction changes
508 }
509 directions[corners++] = nextDirection;
510 // opposite lines must point in opposite directions; xoring them should equal 2
511 if (corners == 3) {
512 if ((directions[0] ^ directions[2]) != 2) {
513 return false;
514 }
515 } else if (corners == 4) {
516 if ((directions[1] ^ directions[3]) != 2) {
517 return false;
518 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
520 break;
521 }
522 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000523 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000524 case kCubic_Verb:
525 return false; // quadratic, cubic not allowed
526 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400527 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700528 insertClose = true;
529 *currVerb -= 1; // try move again afterwards
530 goto addMissingClose;
531 }
Cary Clark8540e112018-04-11 14:30:27 -0400532 if (!addedLine) {
533 firstPt = pts;
534 accumulatingRect = true;
535 } else {
536 accumulatingRect = false;
537 }
Cary Clark88ba9712018-04-12 14:00:24 -0400538 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000539 closedOrMoved = true;
540 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000541 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000542 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000543 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000544 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000545 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700546addMissingClose:
547 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000548 }
549 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400550 if (corners < 3 || corners > 4) {
551 return false;
552 }
553 SkPoint closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400554 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400555 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000556 if (!result) {
557 // check if we are just an incomplete rectangle, in which case we can
558 // return true, but not claim to be closed.
559 // e.g.
560 // 3 sided rectangle
561 // 4 sided but the last edge is not long enough to reach the start
562 //
Cary Clark8540e112018-04-11 14:30:27 -0400563 if (closeXY.fX && closeXY.fY) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000564 return false; // we're diagonal, abort (can we ever reach this?)
565 }
Cary Clark8540e112018-04-11 14:30:27 -0400566 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000567 // make sure the close-segment doesn't double-back on itself
Cary Clark48c464a2018-04-16 12:06:07 -0400568 if (3 == corners || (closeDirection ^ directions[1]) == 2) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000569 result = true;
570 autoClose = false; // we are not closed
571 }
572 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000573 if (savePts) {
574 *ptsPtr = savePts;
575 }
Cary Clark5c715182018-04-09 16:07:11 -0400576 if (result && rect) {
Cary Clark8540e112018-04-11 14:30:27 -0400577 ptrdiff_t count = lastPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400578 rect->set(firstPt, (int) count);
579 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000580 if (result && isClosed) {
581 *isClosed = autoClose;
582 }
583 if (result && direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400584 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000585 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000586 return result;
587}
588
robertphillips4f662e62014-12-29 14:06:51 -0800589bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000590 SkDEBUGCODE(this->validate();)
591 int currVerb = 0;
592 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400593 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000594}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000595
caryclark95bc5f32015-04-08 08:34:15 -0700596bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000597 SkDEBUGCODE(this->validate();)
598 int currVerb = 0;
599 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000600 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400601 SkRect testRects[2];
602 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000603 return false;
604 }
Cary Clark5c715182018-04-09 16:07:11 -0400605 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000606 if (testRects[0].contains(testRects[1])) {
607 if (rects) {
608 rects[0] = testRects[0];
609 rects[1] = testRects[1];
610 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000611 if (dirs) {
612 dirs[0] = testDirs[0];
613 dirs[1] = testDirs[1];
614 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000615 return true;
616 }
617 if (testRects[1].contains(testRects[0])) {
618 if (rects) {
619 rects[0] = testRects[1];
620 rects[1] = testRects[0];
621 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000622 if (dirs) {
623 dirs[0] = testDirs[1];
624 dirs[1] = testDirs[0];
625 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000626 return true;
627 }
628 }
629 return false;
630}
631
Mike Reed0c3137c2018-02-20 13:57:05 -0500632bool SkPath::isOval(SkRect* bounds) const {
633 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
634}
635
636bool SkPath::isRRect(SkRRect* rrect) const {
637 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
638}
639
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640int SkPath::countPoints() const {
641 return fPathRef->countPoints();
642}
643
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000644int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645 SkDEBUGCODE(this->validate();)
646
647 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000648 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800650 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652}
653
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000654SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
656 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000657 }
658 return SkPoint::Make(0, 0);
659}
660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661int SkPath::countVerbs() const {
662 return fPathRef->countVerbs();
663}
664
665static inline void copy_verbs_reverse(uint8_t* inorderDst,
666 const uint8_t* reversedSrc,
667 int count) {
668 for (int i = 0; i < count; ++i) {
669 inorderDst[i] = reversedSrc[~i];
670 }
671}
672
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000673int SkPath::getVerbs(uint8_t dst[], int max) const {
674 SkDEBUGCODE(this->validate();)
675
676 SkASSERT(max >= 0);
677 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000678 int count = SkMin32(max, fPathRef->countVerbs());
679 copy_verbs_reverse(dst, fPathRef->verbs(), count);
680 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000681}
682
reed@google.com294dd7b2011-10-11 11:58:32 +0000683bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 SkDEBUGCODE(this->validate();)
685
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000687 if (count > 0) {
688 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000691 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000693 if (lastPt) {
694 lastPt->set(0, 0);
695 }
696 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697}
698
caryclarkaec25102015-04-29 08:28:30 -0700699void SkPath::setPt(int index, SkScalar x, SkScalar y) {
700 SkDEBUGCODE(this->validate();)
701
702 int count = fPathRef->countPoints();
703 if (count <= index) {
704 return;
705 } else {
706 SkPathRef::Editor ed(&fPathRef);
707 ed.atPoint(index)->set(x, y);
708 }
709}
710
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711void SkPath::setLastPt(SkScalar x, SkScalar y) {
712 SkDEBUGCODE(this->validate();)
713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 if (count == 0) {
716 this->moveTo(x, y);
717 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000718 SkPathRef::Editor ed(&fPathRef);
719 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 }
721}
722
reed@google.com04863fa2011-05-15 04:08:24 +0000723void SkPath::setConvexity(Convexity c) {
724 if (fConvexity != c) {
725 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000726 }
727}
728
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729//////////////////////////////////////////////////////////////////////////////
730// Construction methods
731
reed026beb52015-06-10 14:23:15 -0700732#define DIRTY_AFTER_EDIT \
733 do { \
734 fConvexity = kUnknown_Convexity; \
735 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000736 } while (0)
737
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738void SkPath::incReserve(U16CPU inc) {
739 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741 SkDEBUGCODE(this->validate();)
742}
743
744void SkPath::moveTo(SkScalar x, SkScalar y) {
745 SkDEBUGCODE(this->validate();)
746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000749 // remember our index
750 fLastMoveToIndex = fPathRef->countPoints();
751
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000752 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700753
754 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755}
756
757void SkPath::rMoveTo(SkScalar x, SkScalar y) {
758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->moveTo(pt.fX + x, pt.fY + y);
761}
762
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000763void SkPath::injectMoveToIfNeeded() {
764 if (fLastMoveToIndex < 0) {
765 SkScalar x, y;
766 if (fPathRef->countVerbs() == 0) {
767 x = y = 0;
768 } else {
769 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
770 x = pt.fX;
771 y = pt.fY;
772 }
773 this->moveTo(x, y);
774 }
775}
776
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777void SkPath::lineTo(SkScalar x, SkScalar y) {
778 SkDEBUGCODE(this->validate();)
779
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000780 this->injectMoveToIfNeeded();
781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000782 SkPathRef::Editor ed(&fPathRef);
783 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784
reed@google.comb54455e2011-05-16 14:16:04 +0000785 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786}
787
788void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000789 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 SkPoint pt;
791 this->getLastPt(&pt);
792 this->lineTo(pt.fX + x, pt.fY + y);
793}
794
795void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
796 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000797
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000798 this->injectMoveToIfNeeded();
799
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000800 SkPathRef::Editor ed(&fPathRef);
801 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 pts[0].set(x1, y1);
803 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000804
reed@google.comb54455e2011-05-16 14:16:04 +0000805 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806}
807
808void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
813}
814
reed@google.com277c3f82013-05-31 15:17:50 +0000815void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
816 SkScalar w) {
817 // check for <= 0 or NaN with this test
818 if (!(w > 0)) {
819 this->lineTo(x2, y2);
820 } else if (!SkScalarIsFinite(w)) {
821 this->lineTo(x1, y1);
822 this->lineTo(x2, y2);
823 } else if (SK_Scalar1 == w) {
824 this->quadTo(x1, y1, x2, y2);
825 } else {
826 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000827
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000828 this->injectMoveToIfNeeded();
829
reed@google.com277c3f82013-05-31 15:17:50 +0000830 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000831 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000832 pts[0].set(x1, y1);
833 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000834
reed@google.com277c3f82013-05-31 15:17:50 +0000835 DIRTY_AFTER_EDIT;
836 }
837}
838
839void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
840 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000841 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000842 SkPoint pt;
843 this->getLastPt(&pt);
844 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
845}
846
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
848 SkScalar x3, SkScalar y3) {
849 SkDEBUGCODE(this->validate();)
850
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000851 this->injectMoveToIfNeeded();
852
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000853 SkPathRef::Editor ed(&fPathRef);
854 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 pts[0].set(x1, y1);
856 pts[1].set(x2, y2);
857 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858
reed@google.comb54455e2011-05-16 14:16:04 +0000859 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860}
861
862void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
863 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000864 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 SkPoint pt;
866 this->getLastPt(&pt);
867 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
868 pt.fX + x3, pt.fY + y3);
869}
870
871void SkPath::close() {
872 SkDEBUGCODE(this->validate();)
873
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000876 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 case kLine_Verb:
878 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000879 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 case kMove_Verb: {
882 SkPathRef::Editor ed(&fPathRef);
883 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000885 }
reed@google.com277c3f82013-05-31 15:17:50 +0000886 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000887 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000888 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000889 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000890 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000891 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 }
893 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000894
895 // signal that we need a moveTo to follow us (unless we're done)
896#if 0
897 if (fLastMoveToIndex >= 0) {
898 fLastMoveToIndex = ~fLastMoveToIndex;
899 }
900#else
901 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
902#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000903}
904
905///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000906
fmalitac08d53e2015-11-17 09:53:29 -0800907namespace {
908
909template <unsigned N>
910class PointIterator {
911public:
912 PointIterator(SkPath::Direction dir, unsigned startIndex)
913 : fCurrent(startIndex % N)
914 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
915
916 const SkPoint& current() const {
917 SkASSERT(fCurrent < N);
918 return fPts[fCurrent];
919 }
920
921 const SkPoint& next() {
922 fCurrent = (fCurrent + fAdvance) % N;
923 return this->current();
924 }
925
926protected:
927 SkPoint fPts[N];
928
929private:
930 unsigned fCurrent;
931 unsigned fAdvance;
932};
933
934class RectPointIterator : public PointIterator<4> {
935public:
936 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
937 : PointIterator(dir, startIndex) {
938
939 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
940 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
941 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
942 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
943 }
944};
945
946class OvalPointIterator : public PointIterator<4> {
947public:
948 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
949 : PointIterator(dir, startIndex) {
950
951 const SkScalar cx = oval.centerX();
952 const SkScalar cy = oval.centerY();
953
954 fPts[0] = SkPoint::Make(cx, oval.fTop);
955 fPts[1] = SkPoint::Make(oval.fRight, cy);
956 fPts[2] = SkPoint::Make(cx, oval.fBottom);
957 fPts[3] = SkPoint::Make(oval.fLeft, cy);
958 }
959};
960
961class RRectPointIterator : public PointIterator<8> {
962public:
963 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
964 : PointIterator(dir, startIndex) {
965
966 const SkRect& bounds = rrect.getBounds();
967 const SkScalar L = bounds.fLeft;
968 const SkScalar T = bounds.fTop;
969 const SkScalar R = bounds.fRight;
970 const SkScalar B = bounds.fBottom;
971
972 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
973 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
974 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
975 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
976 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
977 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
978 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
979 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
980 }
981};
982
983} // anonymous namespace
984
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000985static void assert_known_direction(int dir) {
986 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
987}
988
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800990 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991}
992
993void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
994 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800995 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
996}
997
998void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000999 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001000 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001001 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001002 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001003 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001004
fmalitac08d53e2015-11-17 09:53:29 -08001005 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001006
fmalitac08d53e2015-11-17 09:53:29 -08001007 const int kVerbs = 5; // moveTo + 3x lineTo + close
1008 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 RectPointIterator iter(rect, dir, startIndex);
1011
1012 this->moveTo(iter.current());
1013 this->lineTo(iter.next());
1014 this->lineTo(iter.next());
1015 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001017
1018 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019}
1020
reed@google.com744faba2012-05-29 19:54:52 +00001021void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1022 SkDEBUGCODE(this->validate();)
1023 if (count <= 0) {
1024 return;
1025 }
1026
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001027 fLastMoveToIndex = fPathRef->countPoints();
1028
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001029 // +close makes room for the extra kClose_Verb
1030 SkPathRef::Editor ed(&fPathRef, count+close, count);
1031
1032 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001033 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001034 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1035 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001036 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037
reed@google.com744faba2012-05-29 19:54:52 +00001038 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001039 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001040 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001041 }
1042
reed@google.com744faba2012-05-29 19:54:52 +00001043 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001044 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001045}
1046
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001047#include "SkGeometry.h"
1048
reedf90ea012015-01-29 12:03:58 -08001049static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1050 SkPoint* pt) {
1051 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052 // Chrome uses this path to move into and out of ovals. If not
1053 // treated as a special case the moves can distort the oval's
1054 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001055 pt->set(oval.fRight, oval.centerY());
1056 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057 } else if (0 == oval.width() && 0 == oval.height()) {
1058 // Chrome will sometimes create 0 radius round rects. Having degenerate
1059 // quad segments in the path prevents the path from being recognized as
1060 // a rect.
1061 // TODO: optimizing the case where only one of width or height is zero
1062 // should also be considered. This case, however, doesn't seem to be
1063 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001064 pt->set(oval.fRight, oval.fTop);
1065 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001066 }
reedf90ea012015-01-29 12:03:58 -08001067 return false;
1068}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069
reedd5d27d92015-02-09 13:54:43 -08001070// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1071//
1072static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1073 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1074 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1075 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001076
1077 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001078 loss in radians-conversion and/or sin/cos, we may end up with coincident
1079 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1080 of drawing a nearly complete circle (good).
1081 e.g. canvas.drawArc(0, 359.99, ...)
1082 -vs- canvas.drawArc(0, 359.9, ...)
1083 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 */
reedd5d27d92015-02-09 13:54:43 -08001085 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001086 SkScalar sw = SkScalarAbs(sweepAngle);
1087 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1088 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1089 // make a guess at a tiny angle (in radians) to tweak by
1090 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1091 // not sure how much will be enough, so we use a loop
1092 do {
1093 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001094 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1095 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001096 }
1097 }
reedd5d27d92015-02-09 13:54:43 -08001098 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1099}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001100
reed9e779d42015-02-17 11:43:14 -08001101/**
1102 * If this returns 0, then the caller should just line-to the singlePt, else it should
1103 * ignore singlePt and append the specified number of conics.
1104 */
reedd5d27d92015-02-09 13:54:43 -08001105static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001106 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1107 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001108 SkMatrix matrix;
1109
1110 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1111 matrix.postTranslate(oval.centerX(), oval.centerY());
1112
reed9e779d42015-02-17 11:43:14 -08001113 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1114 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001115 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001116 }
1117 return count;
reedd5d27d92015-02-09 13:54:43 -08001118}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001119
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001120void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122 SkRRect rrect;
1123 rrect.setRectRadii(rect, (const SkVector*) radii);
1124 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125}
1126
reed@google.com4ed0fb72012-12-12 20:48:18 +00001127void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001128 // legacy start indices: 6 (CW) and 7(CCW)
1129 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1130}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001131
fmalitac08d53e2015-11-17 09:53:29 -08001132void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1133 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001134
caryclarkda707bf2015-11-19 14:47:43 -08001135 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001136 const SkRect& bounds = rrect.getBounds();
1137
Brian Salomon0a241ce2017-12-15 11:31:05 -05001138 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001139 // degenerate(rect) => radii points are collapsing
1140 this->addRect(bounds, dir, (startIndex + 1) / 2);
1141 } else if (rrect.isOval()) {
1142 // degenerate(oval) => line points are collapsing
1143 this->addOval(bounds, dir, startIndex / 2);
1144 } else {
1145 fFirstDirection = this->hasOnlyMoveTos() ?
1146 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1147
1148 SkAutoPathBoundsUpdate apbu(this, bounds);
1149 SkAutoDisableDirectionCheck addc(this);
1150
1151 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1152 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1153 const SkScalar weight = SK_ScalarRoot2Over2;
1154
1155 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1156 const int kVerbs = startsWithConic
1157 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1158 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1159 this->incReserve(kVerbs);
1160
1161 RRectPointIterator rrectIter(rrect, dir, startIndex);
1162 // Corner iterator indices follow the collapsed radii model,
1163 // adjusted such that the start pt is "behind" the radii start pt.
1164 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1165 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1166
1167 this->moveTo(rrectIter.current());
1168 if (startsWithConic) {
1169 for (unsigned i = 0; i < 3; ++i) {
1170 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1171 this->lineTo(rrectIter.next());
1172 }
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 // final lineTo handled by close().
1175 } else {
1176 for (unsigned i = 0; i < 4; ++i) {
1177 this->lineTo(rrectIter.next());
1178 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1179 }
1180 }
1181 this->close();
1182
caryclarkda707bf2015-11-19 14:47:43 -08001183 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001184 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001185
fmalitac08d53e2015-11-17 09:53:29 -08001186 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1187 }
1188
1189 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001190}
1191
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001192bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001193 int count = fPathRef->countVerbs();
1194 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1195 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001196 if (*verbs == kLine_Verb ||
1197 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001198 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001199 *verbs == kCubic_Verb) {
1200 return false;
1201 }
1202 ++verbs;
1203 }
1204 return true;
1205}
1206
Brian Osmana2318572017-07-10 15:09:26 -04001207bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1208 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001209 if (count < 2) {
1210 return true;
1211 }
Brian Osmana2318572017-07-10 15:09:26 -04001212 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001213 const SkPoint& first = *pts;
1214 for (int index = 1; index < count; ++index) {
1215 if (first != pts[index]) {
1216 return false;
1217 }
1218 }
1219 return true;
1220}
1221
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001222void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1223 Direction dir) {
1224 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001225
humper@google.com75e3ca12013-04-08 21:44:11 +00001226 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001227 return;
1228 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001229
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001230 SkRRect rrect;
1231 rrect.setRectXY(rect, rx, ry);
1232 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001233}
1234
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001236 // legacy start index: 1
1237 this->addOval(oval, dir, 1);
1238}
1239
1240void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001241 assert_known_direction(dir);
1242
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001243 /* If addOval() is called after previous moveTo(),
1244 this path is still marked as an oval. This is used to
1245 fit into WebKit's calling sequences.
1246 We can't simply check isEmpty() in this case, as additional
1247 moveTo() would mark the path non empty.
1248 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001249 bool isOval = hasOnlyMoveTos();
1250 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001251 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001252 } else {
reed026beb52015-06-10 14:23:15 -07001253 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001255
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001256 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 SkAutoPathBoundsUpdate apbu(this, oval);
1258
fmalitac08d53e2015-11-17 09:53:29 -08001259 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1260 const int kVerbs = 6; // moveTo + 4x conicTo + close
1261 this->incReserve(kVerbs);
1262
1263 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1264 // The corner iterator pts are tracking "behind" the oval/radii pts.
1265 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001266 const SkScalar weight = SK_ScalarRoot2Over2;
1267
fmalitac08d53e2015-11-17 09:53:29 -08001268 this->moveTo(ovalIter.current());
1269 for (unsigned i = 0; i < 4; ++i) {
1270 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001271 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273
fmalitac08d53e2015-11-17 09:53:29 -08001274 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1275
robertphillips@google.com466310d2013-12-03 16:43:54 +00001276 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001277
bsalomon78d58d12016-05-27 09:17:04 -07001278 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001279}
1280
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1282 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001283 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 }
1285}
1286
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1288 bool forceMoveTo) {
1289 if (oval.width() < 0 || oval.height() < 0) {
1290 return;
1291 }
1292
reedf90ea012015-01-29 12:03:58 -08001293 if (fPathRef->countVerbs() == 0) {
1294 forceMoveTo = true;
1295 }
1296
1297 SkPoint lonePt;
1298 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1299 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1300 return;
1301 }
1302
reedd5d27d92015-02-09 13:54:43 -08001303 SkVector startV, stopV;
1304 SkRotationDirection dir;
1305 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1306
reed9e779d42015-02-17 11:43:14 -08001307 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001308
1309 // At this point, we know that the arc is not a lone point, but startV == stopV
1310 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1311 // cannot handle it.
1312 if (startV == stopV) {
1313 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1314 SkScalar radiusX = oval.width() / 2;
1315 SkScalar radiusY = oval.height() / 2;
1316 // We cannot use SkScalarSinCos function in the next line because
1317 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1318 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001319 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001320 // make sin(endAngle) to be 0 which will then draw a dot.
1321 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1322 oval.centerY() + radiusY * sk_float_sin(endAngle));
1323 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1324 return;
1325 }
1326
reedd5d27d92015-02-09 13:54:43 -08001327 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001328 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001329 if (count) {
1330 this->incReserve(count * 2 + 1);
1331 const SkPoint& pt = conics[0].fPts[0];
1332 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1333 for (int i = 0; i < count; ++i) {
1334 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1335 }
reed9e779d42015-02-17 11:43:14 -08001336 } else {
1337 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001338 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339}
1340
caryclark55d49052016-01-23 05:07:04 -08001341// This converts the SVG arc to conics.
1342// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1343// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1344// See also SVG implementation notes:
1345// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1346// Note that arcSweep bool value is flipped from the original implementation.
1347void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1348 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001349 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001350 SkPoint srcPts[2];
1351 this->getLastPt(&srcPts[0]);
1352 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1353 // joining the endpoints.
1354 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1355 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001356 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001357 return;
1358 }
1359 // If the current point and target point for the arc are identical, it should be treated as a
1360 // zero length path. This ensures continuity in animations.
1361 srcPts[1].set(x, y);
1362 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001363 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001364 return;
1365 }
1366 rx = SkScalarAbs(rx);
1367 ry = SkScalarAbs(ry);
1368 SkVector midPointDistance = srcPts[0] - srcPts[1];
1369 midPointDistance *= 0.5f;
1370
1371 SkMatrix pointTransform;
1372 pointTransform.setRotate(-angle);
1373
1374 SkPoint transformedMidPoint;
1375 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1376 SkScalar squareRx = rx * rx;
1377 SkScalar squareRy = ry * ry;
1378 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1379 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1380
1381 // Check if the radii are big enough to draw the arc, scale radii if not.
1382 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1383 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1384 if (radiiScale > 1) {
1385 radiiScale = SkScalarSqrt(radiiScale);
1386 rx *= radiiScale;
1387 ry *= radiiScale;
1388 }
1389
1390 pointTransform.setScale(1 / rx, 1 / ry);
1391 pointTransform.preRotate(-angle);
1392
1393 SkPoint unitPts[2];
1394 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1395 SkVector delta = unitPts[1] - unitPts[0];
1396
1397 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1398 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1399
1400 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1401 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1402 scaleFactor = -scaleFactor;
1403 }
1404 delta.scale(scaleFactor);
1405 SkPoint centerPoint = unitPts[0] + unitPts[1];
1406 centerPoint *= 0.5f;
1407 centerPoint.offset(-delta.fY, delta.fX);
1408 unitPts[0] -= centerPoint;
1409 unitPts[1] -= centerPoint;
1410 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1411 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1412 SkScalar thetaArc = theta2 - theta1;
1413 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1414 thetaArc += SK_ScalarPI * 2;
1415 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1416 thetaArc -= SK_ScalarPI * 2;
1417 }
1418 pointTransform.setRotate(angle);
1419 pointTransform.preScale(rx, ry);
1420
Cary Clark1acd3c72017-12-08 11:37:01 -05001421#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001422 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001423#else
1424 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1425 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1426#endif
caryclark55d49052016-01-23 05:07:04 -08001427 SkScalar thetaWidth = thetaArc / segments;
1428 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1429 if (!SkScalarIsFinite(t)) {
1430 return;
1431 }
1432 SkScalar startTheta = theta1;
1433 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001434#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1435 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1436 return scalar == SkScalarFloorToScalar(scalar);
1437 };
1438 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1439 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1440 scalar_is_integer(x) && scalar_is_integer(y);
1441#endif
caryclark55d49052016-01-23 05:07:04 -08001442 for (int i = 0; i < segments; ++i) {
1443 SkScalar endTheta = startTheta + thetaWidth;
1444 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1445
1446 unitPts[1].set(cosEndTheta, sinEndTheta);
1447 unitPts[1] += centerPoint;
1448 unitPts[0] = unitPts[1];
1449 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1450 SkPoint mapped[2];
1451 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001452 /*
1453 Computing the arc width introduces rounding errors that cause arcs to start
1454 outside their marks. A round rect may lose convexity as a result. If the input
1455 values are on integers, place the conic on integers as well.
1456 */
1457#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1458 if (expectIntegers) {
1459 SkScalar* mappedScalars = &mapped[0].fX;
1460 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1461 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1462 }
1463 }
1464#endif
caryclark55d49052016-01-23 05:07:04 -08001465 this->conicTo(mapped[0], mapped[1], w);
1466 startTheta = endTheta;
1467 }
1468}
1469
1470void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1471 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1472 SkPoint currentPoint;
1473 this->getLastPt(&currentPoint);
1474 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1475}
1476
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001477void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 if (oval.isEmpty() || 0 == sweepAngle) {
1479 return;
1480 }
1481
1482 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1483
1484 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001485 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1486 // See SkPath::addOval() docs.
1487 SkScalar startOver90 = startAngle / 90.f;
1488 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1489 SkScalar error = startOver90 - startOver90I;
1490 if (SkScalarNearlyEqual(error, 0)) {
1491 // Index 1 is at startAngle == 0.
1492 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1493 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1494 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1495 (unsigned) startIndex);
1496 return;
1497 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498 }
bsalomon1978ce22016-05-31 10:42:16 -07001499 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500}
1501
1502/*
1503 Need to handle the case when the angle is sharp, and our computed end-points
1504 for the arc go behind pt1 and/or p2...
1505*/
reedc7789042015-01-29 12:59:11 -08001506void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001507 if (radius == 0) {
1508 this->lineTo(x1, y1);
1509 return;
1510 }
1511
1512 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001513
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 // need to know our prev pt so we can construct tangent vectors
1515 {
1516 SkPoint start;
1517 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001518 // Handle degenerate cases by adding a line to the first point and
1519 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 before.setNormalize(x1 - start.fX, y1 - start.fY);
1521 after.setNormalize(x2 - x1, y2 - y1);
1522 }
reed@google.comabf15c12011-01-18 20:35:51 +00001523
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 SkScalar cosh = SkPoint::DotProduct(before, after);
1525 SkScalar sinh = SkPoint::CrossProduct(before, after);
1526
1527 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001528 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 return;
1530 }
reed@google.comabf15c12011-01-18 20:35:51 +00001531
Mike Reeda99b6ce2017-02-04 11:04:26 -05001532 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533
Mike Reeda99b6ce2017-02-04 11:04:26 -05001534 SkScalar xx = x1 - dist * before.fX;
1535 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001536 after.setLength(dist);
1537 this->lineTo(xx, yy);
1538 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1539 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540}
1541
1542///////////////////////////////////////////////////////////////////////////////
1543
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001544void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 SkMatrix matrix;
1546
1547 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001548 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549}
1550
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001551void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001554 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 SkPoint pts[4];
1556 Verb verb;
1557
Cary Clark9480d822017-10-19 18:01:13 -04001558 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001559 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 while ((verb = iter.next(pts)) != kDone_Verb) {
1561 switch (verb) {
1562 case kMove_Verb:
1563 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001564 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1565 injectMoveToIfNeeded(); // In case last contour is closed
1566 this->lineTo(pts[0]);
1567 } else {
1568 this->moveTo(pts[0]);
1569 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570 break;
1571 case kLine_Verb:
1572 proc(matrix, &pts[1], &pts[1], 1);
1573 this->lineTo(pts[1]);
1574 break;
1575 case kQuad_Verb:
1576 proc(matrix, &pts[1], &pts[1], 2);
1577 this->quadTo(pts[1], pts[2]);
1578 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001579 case kConic_Verb:
1580 proc(matrix, &pts[1], &pts[1], 2);
1581 this->conicTo(pts[1], pts[2], iter.conicWeight());
1582 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 case kCubic_Verb:
1584 proc(matrix, &pts[1], &pts[1], 3);
1585 this->cubicTo(pts[1], pts[2], pts[3]);
1586 break;
1587 case kClose_Verb:
1588 this->close();
1589 break;
1590 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001591 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001593 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 }
1595}
1596
1597///////////////////////////////////////////////////////////////////////////////
1598
reed@google.com277c3f82013-05-31 15:17:50 +00001599static int pts_in_verb(unsigned verb) {
1600 static const uint8_t gPtsInVerb[] = {
1601 1, // kMove
1602 1, // kLine
1603 2, // kQuad
1604 2, // kConic
1605 3, // kCubic
1606 0, // kClose
1607 0 // kDone
1608 };
1609
1610 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1611 return gPtsInVerb[verb];
1612}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614// ignore the last point of the 1st contour
1615void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001616 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1617 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618 return;
1619 }
caryclark51c56782016-11-07 05:09:28 -08001620 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1621 SkASSERT(verbsEnd[0] == kMove_Verb);
1622 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1623 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624
caryclark51c56782016-11-07 05:09:28 -08001625 while (verbs < verbsEnd) {
1626 uint8_t v = *verbs++;
1627 pts -= pts_in_verb(v);
1628 switch (v) {
1629 case kMove_Verb:
1630 // if the path has multiple contours, stop after reversing the last
1631 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001633 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634 break;
1635 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001636 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001638 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001639 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001640 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001642 this->cubicTo(pts[2], pts[1], pts[0]);
1643 break;
1644 case kClose_Verb:
1645 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 break;
1647 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001648 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
1650 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 }
1652}
1653
reed@google.com63d73742012-01-10 15:33:12 +00001654void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001655 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001656
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001657 const SkPoint* pts = src.fPathRef->pointsEnd();
1658 // we will iterator through src's verbs backwards
1659 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1660 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001661 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001662
1663 bool needMove = true;
1664 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001665 while (verbs < verbsEnd) {
1666 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001667 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001668
1669 if (needMove) {
1670 --pts;
1671 this->moveTo(pts->fX, pts->fY);
1672 needMove = false;
1673 }
1674 pts -= n;
1675 switch (v) {
1676 case kMove_Verb:
1677 if (needClose) {
1678 this->close();
1679 needClose = false;
1680 }
1681 needMove = true;
1682 pts += 1; // so we see the point in "if (needMove)" above
1683 break;
1684 case kLine_Verb:
1685 this->lineTo(pts[0]);
1686 break;
1687 case kQuad_Verb:
1688 this->quadTo(pts[1], pts[0]);
1689 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001690 case kConic_Verb:
1691 this->conicTo(pts[1], pts[0], *--conicWeights);
1692 break;
reed@google.com63d73742012-01-10 15:33:12 +00001693 case kCubic_Verb:
1694 this->cubicTo(pts[2], pts[1], pts[0]);
1695 break;
1696 case kClose_Verb:
1697 needClose = true;
1698 break;
1699 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001700 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001701 }
1702 }
1703}
1704
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705///////////////////////////////////////////////////////////////////////////////
1706
1707void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1708 SkMatrix matrix;
1709
1710 matrix.setTranslate(dx, dy);
1711 this->transform(matrix, dst);
1712}
1713
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1715 int level = 2) {
1716 if (--level >= 0) {
1717 SkPoint tmp[7];
1718
1719 SkChopCubicAtHalf(pts, tmp);
1720 subdivide_cubic_to(path, &tmp[0], level);
1721 subdivide_cubic_to(path, &tmp[3], level);
1722 } else {
1723 path->cubicTo(pts[1], pts[2], pts[3]);
1724 }
1725}
1726
1727void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1728 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001729 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 dst = (SkPath*)this;
1731 }
1732
tomhudson@google.com8d430182011-06-06 19:11:19 +00001733 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 SkPath tmp;
1735 tmp.fFillType = fFillType;
1736
1737 SkPath::Iter iter(*this, false);
1738 SkPoint pts[4];
1739 SkPath::Verb verb;
1740
reed@google.com4a3b7142012-05-16 17:16:46 +00001741 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 switch (verb) {
1743 case kMove_Verb:
1744 tmp.moveTo(pts[0]);
1745 break;
1746 case kLine_Verb:
1747 tmp.lineTo(pts[1]);
1748 break;
1749 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001750 // promote the quad to a conic
1751 tmp.conicTo(pts[1], pts[2],
1752 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001754 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001755 tmp.conicTo(pts[1], pts[2],
1756 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001757 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 case kCubic_Verb:
1759 subdivide_cubic_to(&tmp, pts);
1760 break;
1761 case kClose_Verb:
1762 tmp.close();
1763 break;
1764 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001765 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 break;
1767 }
1768 }
1769
1770 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001771 SkPathRef::Editor ed(&dst->fPathRef);
1772 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001773 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1776
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001779 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001780 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001782
reed026beb52015-06-10 14:23:15 -07001783 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1784 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001785 } else {
1786 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001787 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1788 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001789 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001790 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1791 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001793 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001794 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001795 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001796 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001797 }
1798 }
1799
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 SkDEBUGCODE(dst->validate();)
1801 }
1802}
1803
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804///////////////////////////////////////////////////////////////////////////////
1805///////////////////////////////////////////////////////////////////////////////
1806
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001808 kEmptyContour_SegmentState, // The current contour is empty. We may be
1809 // starting processing or we may have just
1810 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1812 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1813 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814};
1815
1816SkPath::Iter::Iter() {
1817#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001818 fPts = nullptr;
1819 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001821 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001822 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823#endif
1824 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001825 fVerbs = nullptr;
1826 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 fNeedClose = false;
1828}
1829
1830SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1831 this->setPath(path, forceClose);
1832}
1833
1834void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001835 fPts = path.fPathRef->points();
1836 fVerbs = path.fPathRef->verbs();
1837 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001838 fConicWeights = path.fPathRef->conicWeights();
1839 if (fConicWeights) {
1840 fConicWeights -= 1; // begin one behind
1841 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001843 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 fForceClose = SkToU8(forceClose);
1845 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001846 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847}
1848
1849bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001850 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 return false;
1852 }
1853 if (fForceClose) {
1854 return true;
1855 }
1856
1857 const uint8_t* verbs = fVerbs;
1858 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001859
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 if (kMove_Verb == *(verbs - 1)) {
1861 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 }
1863
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864 while (verbs > stop) {
1865 // verbs points one beyond the current verb, decrement first.
1866 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 if (kMove_Verb == v) {
1868 break;
1869 }
1870 if (kClose_Verb == v) {
1871 return true;
1872 }
1873 }
1874 return false;
1875}
1876
1877SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001878 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001880 // A special case: if both points are NaN, SkPoint::operation== returns
1881 // false, but the iterator expects that they are treated as the same.
1882 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001883 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1884 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001885 return kClose_Verb;
1886 }
1887
reed@google.com9e25dbf2012-05-15 17:05:38 +00001888 pts[0] = fLastPt;
1889 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 fLastPt = fMoveTo;
1891 fCloseLine = true;
1892 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001893 } else {
1894 pts[0] = fMoveTo;
1895 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897}
1898
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 if (fSegmentState == kAfterMove_SegmentState) {
1901 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001903 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1906 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909}
1910
caryclarke8c56662015-07-14 11:19:26 -07001911void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 // We need to step over anything that will not move the current draw point
1913 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001914 const uint8_t* lastMoveVerb = nullptr;
1915 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001916 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 SkPoint lastPt = fLastPt;
1918 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001919 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 switch (verb) {
1921 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 // Keep a record of this most recent move
1923 lastMoveVerb = fVerbs;
1924 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001925 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001926 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001927 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 fPts++;
1929 break;
1930
1931 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001932 // A close when we are in a segment is always valid except when it
1933 // follows a move which follows a segment.
1934 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 return;
1936 }
1937 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001938 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 break;
1940
1941 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001942 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 if (lastMoveVerb) {
1944 fVerbs = lastMoveVerb;
1945 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001946 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 return;
1948 }
1949 return;
1950 }
1951 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001952 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001953 fPts++;
1954 break;
1955
reed@google.com277c3f82013-05-31 15:17:50 +00001956 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001958 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 if (lastMoveVerb) {
1960 fVerbs = lastMoveVerb;
1961 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001962 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 return;
1964 }
1965 return;
1966 }
1967 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001968 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001970 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 break;
1972
1973 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001974 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001975 if (lastMoveVerb) {
1976 fVerbs = lastMoveVerb;
1977 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001978 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 return;
1980 }
1981 return;
1982 }
1983 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001984 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 fPts += 3;
1986 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001987
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001989 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001990 }
1991 }
1992}
1993
reed@google.com4a3b7142012-05-16 17:16:46 +00001994SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001995 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001996
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 // Close the curve if requested and if there is some curve to close
1999 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002000 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 return kLine_Verb;
2002 }
2003 fNeedClose = false;
2004 return kClose_Verb;
2005 }
2006 return kDone_Verb;
2007 }
2008
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002009 // fVerbs is one beyond the current verb, decrement first
2010 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002011 const SkPoint* SK_RESTRICT srcPts = fPts;
2012 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013
2014 switch (verb) {
2015 case kMove_Verb:
2016 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002017 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 verb = this->autoClose(pts);
2019 if (verb == kClose_Verb) {
2020 fNeedClose = false;
2021 }
2022 return (Verb)verb;
2023 }
2024 if (fVerbs == fVerbStop) { // might be a trailing moveto
2025 return kDone_Verb;
2026 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002027 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002028 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002030 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002031 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 fNeedClose = fForceClose;
2033 break;
2034 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002035 pts[0] = this->cons_moveTo();
2036 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 fLastPt = srcPts[0];
2038 fCloseLine = false;
2039 srcPts += 1;
2040 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002041 case kConic_Verb:
2042 fConicWeights += 1;
2043 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002045 pts[0] = this->cons_moveTo();
2046 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 fLastPt = srcPts[1];
2048 srcPts += 2;
2049 break;
2050 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002051 pts[0] = this->cons_moveTo();
2052 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 fLastPt = srcPts[2];
2054 srcPts += 3;
2055 break;
2056 case kClose_Verb:
2057 verb = this->autoClose(pts);
2058 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002059 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 } else {
2061 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002062 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002064 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 break;
2066 }
2067 fPts = srcPts;
2068 return (Verb)verb;
2069}
2070
2071///////////////////////////////////////////////////////////////////////////////
2072
Ben Wagner4d1955c2017-03-10 13:08:15 -05002073#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002074#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002075#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002076
reed@google.com51bbe752013-01-17 22:07:50 +00002077static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002078 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002079 str->append(label);
2080 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002081
reed@google.com51bbe752013-01-17 22:07:50 +00002082 const SkScalar* values = &pts[0].fX;
2083 count *= 2;
2084
2085 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002086 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002087 if (i < count - 1) {
2088 str->append(", ");
2089 }
2090 }
Mike Reed405b9d22018-01-09 15:11:08 -05002091 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002092 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002093 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002094 }
caryclark08fa28c2014-10-23 13:08:56 -07002095 str->append(");");
reede05fed02014-12-15 07:59:53 -08002096 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002097 str->append(" // ");
2098 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002099 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002100 if (i < count - 1) {
2101 str->append(", ");
2102 }
2103 }
2104 if (conicWeight >= 0) {
2105 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002106 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002107 }
2108 }
2109 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002110}
2111
caryclarke9562592014-09-15 09:26:09 -07002112void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002113 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 Iter iter(*this, forceClose);
2115 SkPoint pts[4];
2116 Verb verb;
2117
reed@google.com51bbe752013-01-17 22:07:50 +00002118 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002119 char const * const gFillTypeStrs[] = {
2120 "Winding",
2121 "EvenOdd",
2122 "InverseWinding",
2123 "InverseEvenOdd",
2124 };
2125 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2126 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002127 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 switch (verb) {
2129 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002130 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 break;
2132 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002133 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134 break;
2135 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002136 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002138 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002139 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002140 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002142 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 break;
2144 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002145 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 break;
2147 default:
2148 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2149 verb = kDone_Verb; // stop the loop
2150 break;
2151 }
caryclark1049f122015-04-20 08:31:59 -07002152 if (!wStream && builder.size()) {
2153 SkDebugf("%s", builder.c_str());
2154 builder.reset();
2155 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 }
caryclark66a5d8b2014-06-24 08:30:15 -07002157 if (wStream) {
2158 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160}
2161
reed@android.come522ca52009-11-23 20:10:41 +00002162void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002163 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002164}
2165
2166void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002167 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002168}
2169
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002170
Cary Clark0461e9f2017-08-25 15:13:38 -04002171bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002172 if ((fFillType & ~3) != 0) {
2173 return false;
2174 }
reed@google.comabf15c12011-01-18 20:35:51 +00002175
djsollen@google.com077348c2012-10-22 20:23:32 +00002176#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002177 if (!fBoundsIsDirty) {
2178 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002179
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002180 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002181 if (SkToBool(fIsFinite) != isFinite) {
2182 return false;
2183 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002184
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002185 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002186 // if we're empty, fBounds may be empty but translated, so we can't
2187 // necessarily compare to bounds directly
2188 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2189 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002190 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2191 return false;
2192 }
reed@android.come522ca52009-11-23 20:10:41 +00002193 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002194 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002195 if (!fBounds.isEmpty()) {
2196 return false;
2197 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002198 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002199 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002200 if (!fBounds.contains(bounds)) {
2201 return false;
2202 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002203 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002204 }
reed@android.come522ca52009-11-23 20:10:41 +00002205 }
2206 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002207#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002208 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002209}
reed@android.come522ca52009-11-23 20:10:41 +00002210
reed@google.com04863fa2011-05-15 04:08:24 +00002211///////////////////////////////////////////////////////////////////////////////
2212
reed@google.com0b7b9822011-05-16 12:29:27 +00002213static int sign(SkScalar x) { return x < 0; }
2214#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002215
robertphillipsc506e302014-09-16 09:43:31 -07002216enum DirChange {
2217 kLeft_DirChange,
2218 kRight_DirChange,
2219 kStraight_DirChange,
2220 kBackwards_DirChange,
2221
2222 kInvalid_DirChange
2223};
2224
2225
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002226static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002227 // The error epsilon was empirically derived; worse case round rects
2228 // with a mid point outset by 2x float epsilon in tests had an error
2229 // of 12.
2230 const int epsilon = 16;
2231 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2232 return false;
2233 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002234 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002235 int aBits = SkFloatAs2sCompliment(compA);
2236 int bBits = SkFloatAs2sCompliment(compB);
2237 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002238}
2239
caryclarkb4216502015-03-02 13:02:34 -08002240static bool approximately_zero_when_compared_to(double x, double y) {
2241 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002242}
2243
caryclarkb4216502015-03-02 13:02:34 -08002244
reed@google.com04863fa2011-05-15 04:08:24 +00002245// only valid for a single contour
2246struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002247 Convexicator()
2248 : fPtCount(0)
2249 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002250 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002251 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002252 , fIsCurve(false)
2253 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002254 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002255 // warnings
djsollen2f124632016-04-29 13:53:05 -07002256 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002257 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002258 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002259 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002260 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002261
2262 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002263 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 }
2265
2266 SkPath::Convexity getConvexity() const { return fConvexity; }
2267
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002268 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002269 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270
reed@google.com04863fa2011-05-15 04:08:24 +00002271 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002272 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002273 return;
2274 }
2275
2276 if (0 == fPtCount) {
2277 fCurrPt = pt;
2278 ++fPtCount;
2279 } else {
2280 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002281 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002282 if (!SkScalarIsFinite(lengthSqd)) {
2283 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002284 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002285 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002286 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002287 fCurrPt = pt;
2288 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002289 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002290 } else {
2291 SkASSERT(fPtCount > 2);
2292 this->addVec(vec);
2293 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002294
reed@google.com85b6e392011-05-15 20:25:17 +00002295 int sx = sign(vec.fX);
2296 int sy = sign(vec.fY);
2297 fDx += (sx != fSx);
2298 fDy += (sy != fSy);
2299 fSx = sx;
2300 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002301
reed@google.com85b6e392011-05-15 20:25:17 +00002302 if (fDx > 3 || fDy > 3) {
2303 fConvexity = SkPath::kConcave_Convexity;
2304 }
reed@google.com04863fa2011-05-15 04:08:24 +00002305 }
2306 }
2307 }
2308
2309 void close() {
2310 if (fPtCount > 2) {
2311 this->addVec(fFirstVec);
2312 }
2313 }
2314
caryclarkb4216502015-03-02 13:02:34 -08002315 DirChange directionChange(const SkVector& curVec) {
2316 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2317
2318 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2319 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2320 largest = SkTMax(largest, -smallest);
2321
2322 if (!almost_equal(largest, largest + cross)) {
2323 int sign = SkScalarSignAsInt(cross);
2324 if (sign) {
2325 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2326 }
2327 }
2328
2329 if (cross) {
2330 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2331 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2332 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2333 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2334 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2335 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2336 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2337 if (sign) {
2338 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2339 }
2340 }
2341 }
2342
Cary Clarkdf429f32017-11-08 11:44:31 -05002343 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2344 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2345 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2346 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002347 fLastVec.dot(curVec) < 0.0f) {
2348 return kBackwards_DirChange;
2349 }
2350
2351 return kStraight_DirChange;
2352 }
2353
Cary Clarkc8323aa2017-08-25 08:04:43 -04002354 bool hasBackwards() const {
2355 return fBackwards;
2356 }
caryclarkb4216502015-03-02 13:02:34 -08002357
caryclarkd3d1a982014-12-08 04:57:38 -08002358 bool isFinite() const {
2359 return fIsFinite;
2360 }
2361
caryclark5ccef572015-03-02 10:07:56 -08002362 void setCurve(bool isCurve) {
2363 fIsCurve = isCurve;
2364 }
2365
reed@google.com04863fa2011-05-15 04:08:24 +00002366private:
2367 void addVec(const SkVector& vec) {
2368 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002369 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002370 switch (dir) {
2371 case kLeft_DirChange: // fall through
2372 case kRight_DirChange:
2373 if (kInvalid_DirChange == fExpectedDir) {
2374 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002375 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2376 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002377 } else if (dir != fExpectedDir) {
2378 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002379 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002380 }
2381 fLastVec = vec;
2382 break;
2383 case kStraight_DirChange:
2384 break;
2385 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002386 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002387 // If any of the subsequent dir is non-backward, it'll be concave.
2388 // Otherwise, it's still convex.
2389 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002390 }
robertphillipsc506e302014-09-16 09:43:31 -07002391 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002392 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002393 break;
2394 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002395 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002396 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002397 }
2398 }
2399
caryclarkb4216502015-03-02 13:02:34 -08002400 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002401 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002403 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2404 // value with the current vec is deemed to be of a significant value.
2405 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002407 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002408 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002409 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002410 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002411 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002412 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002413 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002414};
2415
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002416SkPath::Convexity SkPath::internalGetConvexity() const {
2417 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002418 SkPoint pts[4];
2419 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002421
2422 int contourCount = 0;
2423 int count;
2424 Convexicator state;
2425
caryclarkd3d1a982014-12-08 04:57:38 -08002426 if (!isFinite()) {
2427 return kUnknown_Convexity;
2428 }
Brian Osman205a1262017-09-18 13:13:48 +00002429 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002430 switch (verb) {
2431 case kMove_Verb:
2432 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002433 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002434 return kConcave_Convexity;
2435 }
2436 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002437 // fall through
2438 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002439 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002440 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002441 break;
caryclark5ccef572015-03-02 10:07:56 -08002442 case kQuad_Verb:
2443 // fall through
2444 case kConic_Verb:
2445 // fall through
2446 case kCubic_Verb:
2447 count = 2 + (kCubic_Verb == verb);
2448 // As an additional enhancement, this could set curve true only
2449 // if the curve is nonlinear
2450 state.setCurve(true);
2451 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002452 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002453 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002454 state.close();
2455 count = 0;
2456 break;
2457 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002458 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002459 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002460 return kConcave_Convexity;
2461 }
2462
2463 for (int i = 1; i <= count; i++) {
2464 state.addPt(pts[i]);
2465 }
2466 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002467 if (!state.isFinite()) {
2468 return kUnknown_Convexity;
2469 }
reed@google.com04863fa2011-05-15 04:08:24 +00002470 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002471 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002472 return kConcave_Convexity;
2473 }
2474 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002475 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002476 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002477 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2478 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2479 fConvexity = Convexity::kConcave_Convexity;
2480 } else {
2481 fFirstDirection = state.getFirstDirection();
2482 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002483 }
2484 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002485}
reed@google.com69a99432012-01-10 18:00:10 +00002486
2487///////////////////////////////////////////////////////////////////////////////
2488
2489class ContourIter {
2490public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002491 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002492
2493 bool done() const { return fDone; }
2494 // if !done() then these may be called
2495 int count() const { return fCurrPtCount; }
2496 const SkPoint* pts() const { return fCurrPt; }
2497 void next();
2498
2499private:
2500 int fCurrPtCount;
2501 const SkPoint* fCurrPt;
2502 const uint8_t* fCurrVerb;
2503 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002504 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002505 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002506 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002507};
2508
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002509ContourIter::ContourIter(const SkPathRef& pathRef) {
2510 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002511 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512 fCurrPt = pathRef.points();
2513 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002514 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002515 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002516 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002517 this->next();
2518}
2519
2520void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002521 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002522 fDone = true;
2523 }
2524 if (fDone) {
2525 return;
2526 }
2527
2528 // skip pts of prev contour
2529 fCurrPt += fCurrPtCount;
2530
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002531 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002532 int ptCount = 1; // moveTo
2533 const uint8_t* verbs = fCurrVerb;
2534
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002535 for (--verbs; verbs > fStopVerbs; --verbs) {
2536 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002537 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002538 goto CONTOUR_END;
2539 case SkPath::kLine_Verb:
2540 ptCount += 1;
2541 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002542 case SkPath::kConic_Verb:
2543 fCurrConicWeight += 1;
2544 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002545 case SkPath::kQuad_Verb:
2546 ptCount += 2;
2547 break;
2548 case SkPath::kCubic_Verb:
2549 ptCount += 3;
2550 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002551 case SkPath::kClose_Verb:
2552 break;
2553 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002554 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002555 break;
2556 }
2557 }
2558CONTOUR_END:
2559 fCurrPtCount = ptCount;
2560 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002561 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002562}
2563
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002564// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002565static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002566 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2567 // We may get 0 when the above subtracts underflow. We expect this to be
2568 // very rare and lazily promote to double.
2569 if (0 == cross) {
2570 double p0x = SkScalarToDouble(p0.fX);
2571 double p0y = SkScalarToDouble(p0.fY);
2572
2573 double p1x = SkScalarToDouble(p1.fX);
2574 double p1y = SkScalarToDouble(p1.fY);
2575
2576 double p2x = SkScalarToDouble(p2.fX);
2577 double p2y = SkScalarToDouble(p2.fY);
2578
2579 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2580 (p1y - p0y) * (p2x - p0x));
2581
2582 }
2583 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002584}
2585
reed@google.comc1ea60a2012-01-31 15:15:36 +00002586// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002587static int find_max_y(const SkPoint pts[], int count) {
2588 SkASSERT(count > 0);
2589 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002590 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002591 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002592 SkScalar y = pts[i].fY;
2593 if (y > max) {
2594 max = y;
2595 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002596 }
2597 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002598 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002599}
2600
reed@google.comcabaf1d2012-01-11 21:03:05 +00002601static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2602 int i = index;
2603 for (;;) {
2604 i = (i + inc) % n;
2605 if (i == index) { // we wrapped around, so abort
2606 break;
2607 }
2608 if (pts[index] != pts[i]) { // found a different point, success!
2609 break;
2610 }
2611 }
2612 return i;
2613}
2614
reed@google.comc1ea60a2012-01-31 15:15:36 +00002615/**
2616 * Starting at index, and moving forward (incrementing), find the xmin and
2617 * xmax of the contiguous points that have the same Y.
2618 */
2619static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2620 int* maxIndexPtr) {
2621 const SkScalar y = pts[index].fY;
2622 SkScalar min = pts[index].fX;
2623 SkScalar max = min;
2624 int minIndex = index;
2625 int maxIndex = index;
2626 for (int i = index + 1; i < n; ++i) {
2627 if (pts[i].fY != y) {
2628 break;
2629 }
2630 SkScalar x = pts[i].fX;
2631 if (x < min) {
2632 min = x;
2633 minIndex = i;
2634 } else if (x > max) {
2635 max = x;
2636 maxIndex = i;
2637 }
2638 }
2639 *maxIndexPtr = maxIndex;
2640 return minIndex;
2641}
2642
reed026beb52015-06-10 14:23:15 -07002643static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2644 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002645}
2646
reed@google.comac8543f2012-01-30 20:51:25 +00002647/*
2648 * We loop through all contours, and keep the computed cross-product of the
2649 * contour that contained the global y-max. If we just look at the first
2650 * contour, we may find one that is wound the opposite way (correctly) since
2651 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2652 * that is outer most (or at least has the global y-max) before we can consider
2653 * its cross product.
2654 */
reed026beb52015-06-10 14:23:15 -07002655bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002656 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2657 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002658 return true;
2659 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002660
2661 // don't want to pay the cost for computing this if it
2662 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002663 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2664 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002665 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002666 return false;
2667 }
reed@google.com69a99432012-01-10 18:00:10 +00002668
reed026beb52015-06-10 14:23:15 -07002669 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002670
reed@google.comac8543f2012-01-30 20:51:25 +00002671 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002672 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002673 SkScalar ymaxCross = 0;
2674
reed@google.com69a99432012-01-10 18:00:10 +00002675 for (; !iter.done(); iter.next()) {
2676 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002677 if (n < 3) {
2678 continue;
2679 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002680
reed@google.comcabaf1d2012-01-11 21:03:05 +00002681 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002682 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002683 int index = find_max_y(pts, n);
2684 if (pts[index].fY < ymax) {
2685 continue;
2686 }
2687
2688 // If there is more than 1 distinct point at the y-max, we take the
2689 // x-min and x-max of them and just subtract to compute the dir.
2690 if (pts[(index + 1) % n].fY == pts[index].fY) {
2691 int maxIndex;
2692 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2693 if (minIndex == maxIndex) {
2694 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002695 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002696 SkASSERT(pts[minIndex].fY == pts[index].fY);
2697 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2698 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2699 // we just subtract the indices, and let that auto-convert to
2700 // SkScalar, since we just want - or + to signal the direction.
2701 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002702 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002703 TRY_CROSSPROD:
2704 // Find a next and prev index to use for the cross-product test,
2705 // but we try to find pts that form non-zero vectors from pts[index]
2706 //
2707 // Its possible that we can't find two non-degenerate vectors, so
2708 // we have to guard our search (e.g. all the pts could be in the
2709 // same place).
2710
2711 // we pass n - 1 instead of -1 so we don't foul up % operator by
2712 // passing it a negative LH argument.
2713 int prev = find_diff_pt(pts, index, n, n - 1);
2714 if (prev == index) {
2715 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002716 continue;
2717 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002718 int next = find_diff_pt(pts, index, n, 1);
2719 SkASSERT(next != index);
2720 cross = cross_prod(pts[prev], pts[index], pts[next]);
2721 // if we get a zero and the points are horizontal, then we look at the spread in
2722 // x-direction. We really should continue to walk away from the degeneracy until
2723 // there is a divergence.
2724 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2725 // construct the subtract so we get the correct Direction below
2726 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002727 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002728 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002729
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002730 if (cross) {
2731 // record our best guess so far
2732 ymax = pts[index].fY;
2733 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002734 }
2735 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002736 if (ymaxCross) {
2737 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002738 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002739 return true;
2740 } else {
2741 return false;
2742 }
reed@google.comac8543f2012-01-30 20:51:25 +00002743}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002744
2745///////////////////////////////////////////////////////////////////////////////
2746
caryclark9aacd902015-12-14 08:38:09 -08002747static bool between(SkScalar a, SkScalar b, SkScalar c) {
2748 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2749 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2750 return (a - b) * (c - b) <= 0;
2751}
2752
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002753static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2754 SkScalar t) {
2755 SkScalar A = c3 + 3*(c1 - c2) - c0;
2756 SkScalar B = 3*(c2 - c1 - c1 + c0);
2757 SkScalar C = 3*(c1 - c0);
2758 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002759 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760}
2761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762template <size_t N> static void find_minmax(const SkPoint pts[],
2763 SkScalar* minPtr, SkScalar* maxPtr) {
2764 SkScalar min, max;
2765 min = max = pts[0].fX;
2766 for (size_t i = 1; i < N; ++i) {
2767 min = SkMinScalar(min, pts[i].fX);
2768 max = SkMaxScalar(max, pts[i].fX);
2769 }
2770 *minPtr = min;
2771 *maxPtr = max;
2772}
2773
caryclark9cb5d752015-12-18 04:35:24 -08002774static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2775 if (start.fY == end.fY) {
2776 return between(start.fX, x, end.fX) && x != end.fX;
2777 } else {
2778 return x == start.fX && y == start.fY;
2779 }
2780}
2781
caryclark9aacd902015-12-14 08:38:09 -08002782static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002783 SkScalar y0 = pts[0].fY;
2784 SkScalar y3 = pts[3].fY;
2785
2786 int dir = 1;
2787 if (y0 > y3) {
2788 SkTSwap(y0, y3);
2789 dir = -1;
2790 }
2791 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 return 0;
2793 }
caryclark9cb5d752015-12-18 04:35:24 -08002794 if (checkOnCurve(x, y, pts[0], pts[3])) {
2795 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002796 return 0;
2797 }
caryclark9cb5d752015-12-18 04:35:24 -08002798 if (y == y3) {
2799 return 0;
2800 }
2801
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002802 // quickreject or quickaccept
2803 SkScalar min, max;
2804 find_minmax<4>(pts, &min, &max);
2805 if (x < min) {
2806 return 0;
2807 }
2808 if (x > max) {
2809 return dir;
2810 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002811
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002812 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002813 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002814 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002815 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002816 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002817 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002818 if (SkScalarNearlyEqual(xt, x)) {
2819 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2820 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002821 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002822 }
2823 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 return xt < x ? dir : 0;
2825}
2826
caryclark9aacd902015-12-14 08:38:09 -08002827static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 SkPoint dst[10];
2829 int n = SkChopCubicAtYExtrema(pts, dst);
2830 int w = 0;
2831 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002832 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 }
2834 return w;
2835}
2836
caryclark9aacd902015-12-14 08:38:09 -08002837static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2838 SkASSERT(src);
2839 SkASSERT(t >= 0 && t <= 1);
2840 SkScalar src2w = src[2] * w;
2841 SkScalar C = src[0];
2842 SkScalar A = src[4] - 2 * src2w + C;
2843 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002844 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002845}
2846
2847
2848static double conic_eval_denominator(SkScalar w, SkScalar t) {
2849 SkScalar B = 2 * (w - 1);
2850 SkScalar C = 1;
2851 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002852 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002853}
2854
2855static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2856 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 SkScalar y0 = pts[0].fY;
2858 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 int dir = 1;
2861 if (y0 > y2) {
2862 SkTSwap(y0, y2);
2863 dir = -1;
2864 }
caryclark9aacd902015-12-14 08:38:09 -08002865 if (y < y0 || y > y2) {
2866 return 0;
2867 }
caryclark9cb5d752015-12-18 04:35:24 -08002868 if (checkOnCurve(x, y, pts[0], pts[2])) {
2869 *onCurveCount += 1;
2870 return 0;
2871 }
caryclark9aacd902015-12-14 08:38:09 -08002872 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 return 0;
2874 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002875
caryclark9aacd902015-12-14 08:38:09 -08002876 SkScalar roots[2];
2877 SkScalar A = pts[2].fY;
2878 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2879 SkScalar C = pts[0].fY;
2880 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2881 B -= C; // B = b*w - w * yCept + yCept - a
2882 C -= y;
2883 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2884 SkASSERT(n <= 1);
2885 SkScalar xt;
2886 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002887 // zero roots are returned only when y0 == y
2888 // Need [0] if dir == 1
2889 // and [2] if dir == -1
2890 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002891 } else {
2892 SkScalar t = roots[0];
2893 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2894 }
2895 if (SkScalarNearlyEqual(xt, x)) {
2896 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2897 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002898 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002899 }
2900 }
2901 return xt < x ? dir : 0;
2902}
2903
2904static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2905 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2906 if (y0 == y1) {
2907 return true;
2908 }
2909 if (y0 < y1) {
2910 return y1 <= y2;
2911 } else {
2912 return y1 >= y2;
2913 }
2914}
2915
2916static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2917 int* onCurveCount) {
2918 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002919 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002920 // If the data points are very large, the conic may not be monotonic but may also
2921 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002922 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2923 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2924 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002925 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2926 }
2927 return w;
2928}
2929
2930static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2931 SkScalar y0 = pts[0].fY;
2932 SkScalar y2 = pts[2].fY;
2933
2934 int dir = 1;
2935 if (y0 > y2) {
2936 SkTSwap(y0, y2);
2937 dir = -1;
2938 }
2939 if (y < y0 || y > y2) {
2940 return 0;
2941 }
caryclark9cb5d752015-12-18 04:35:24 -08002942 if (checkOnCurve(x, y, pts[0], pts[2])) {
2943 *onCurveCount += 1;
2944 return 0;
2945 }
caryclark9aacd902015-12-14 08:38:09 -08002946 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002947 return 0;
2948 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 // bounds check on X (not required. is it faster?)
2950#if 0
2951 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2952 return 0;
2953 }
2954#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002955
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002956 SkScalar roots[2];
2957 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2958 2 * (pts[1].fY - pts[0].fY),
2959 pts[0].fY - y,
2960 roots);
2961 SkASSERT(n <= 1);
2962 SkScalar xt;
2963 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002964 // zero roots are returned only when y0 == y
2965 // Need [0] if dir == 1
2966 // and [2] if dir == -1
2967 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 } else {
2969 SkScalar t = roots[0];
2970 SkScalar C = pts[0].fX;
2971 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2972 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002973 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 }
caryclark9aacd902015-12-14 08:38:09 -08002975 if (SkScalarNearlyEqual(xt, x)) {
2976 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2977 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002978 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002979 }
2980 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002981 return xt < x ? dir : 0;
2982}
2983
caryclark9aacd902015-12-14 08:38:09 -08002984static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 SkPoint dst[5];
2986 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002987
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2989 n = SkChopQuadAtYExtrema(pts, dst);
2990 pts = dst;
2991 }
caryclark9aacd902015-12-14 08:38:09 -08002992 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002994 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 }
2996 return w;
2997}
2998
caryclark9aacd902015-12-14 08:38:09 -08002999static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 SkScalar x0 = pts[0].fX;
3001 SkScalar y0 = pts[0].fY;
3002 SkScalar x1 = pts[1].fX;
3003 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003006
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 int dir = 1;
3008 if (y0 > y1) {
3009 SkTSwap(y0, y1);
3010 dir = -1;
3011 }
caryclark9aacd902015-12-14 08:38:09 -08003012 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 return 0;
3014 }
caryclark9cb5d752015-12-18 04:35:24 -08003015 if (checkOnCurve(x, y, pts[0], pts[1])) {
3016 *onCurveCount += 1;
3017 return 0;
3018 }
3019 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003020 return 0;
3021 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003022 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003023
caryclark9aacd902015-12-14 08:38:09 -08003024 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003025 // zero cross means the point is on the line, and since the case where
3026 // y of the query point is at the end point is handled above, we can be
3027 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003028 if (x != x1 || y != pts[1].fY) {
3029 *onCurveCount += 1;
3030 }
caryclark9aacd902015-12-14 08:38:09 -08003031 dir = 0;
3032 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003033 dir = 0;
3034 }
3035 return dir;
3036}
3037
caryclark9aacd902015-12-14 08:38:09 -08003038static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3039 SkTDArray<SkVector>* tangents) {
3040 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3041 && !between(pts[2].fY, y, pts[3].fY)) {
3042 return;
3043 }
3044 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3045 && !between(pts[2].fX, x, pts[3].fX)) {
3046 return;
3047 }
3048 SkPoint dst[10];
3049 int n = SkChopCubicAtYExtrema(pts, dst);
3050 for (int i = 0; i <= n; ++i) {
3051 SkPoint* c = &dst[i * 3];
3052 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003053 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003054 continue;
mbarbella276e6332016-05-31 14:44:01 -07003055 }
caryclark9aacd902015-12-14 08:38:09 -08003056 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3057 if (!SkScalarNearlyEqual(x, xt)) {
3058 continue;
3059 }
3060 SkVector tangent;
3061 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3062 tangents->push(tangent);
3063 }
3064}
3065
3066static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3067 SkTDArray<SkVector>* tangents) {
3068 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3069 return;
3070 }
3071 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3072 return;
3073 }
3074 SkScalar roots[2];
3075 SkScalar A = pts[2].fY;
3076 SkScalar B = pts[1].fY * w - y * w + y;
3077 SkScalar C = pts[0].fY;
3078 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3079 B -= C; // B = b*w - w * yCept + yCept - a
3080 C -= y;
3081 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3082 for (int index = 0; index < n; ++index) {
3083 SkScalar t = roots[index];
3084 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3085 if (!SkScalarNearlyEqual(x, xt)) {
3086 continue;
3087 }
3088 SkConic conic(pts, w);
3089 tangents->push(conic.evalTangentAt(t));
3090 }
3091}
3092
3093static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3094 SkTDArray<SkVector>* tangents) {
3095 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3096 return;
3097 }
3098 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3099 return;
3100 }
3101 SkScalar roots[2];
3102 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3103 2 * (pts[1].fY - pts[0].fY),
3104 pts[0].fY - y,
3105 roots);
3106 for (int index = 0; index < n; ++index) {
3107 SkScalar t = roots[index];
3108 SkScalar C = pts[0].fX;
3109 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3110 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003111 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003112 if (!SkScalarNearlyEqual(x, xt)) {
3113 continue;
3114 }
3115 tangents->push(SkEvalQuadTangentAt(pts, t));
3116 }
3117}
3118
3119static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3120 SkTDArray<SkVector>* tangents) {
3121 SkScalar y0 = pts[0].fY;
3122 SkScalar y1 = pts[1].fY;
3123 if (!between(y0, y, y1)) {
3124 return;
3125 }
3126 SkScalar x0 = pts[0].fX;
3127 SkScalar x1 = pts[1].fX;
3128 if (!between(x0, x, x1)) {
3129 return;
3130 }
3131 SkScalar dx = x1 - x0;
3132 SkScalar dy = y1 - y0;
3133 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3134 return;
3135 }
3136 SkVector v;
3137 v.set(dx, dy);
3138 tangents->push(v);
3139}
3140
reed@google.com4db592c2013-10-30 17:39:43 +00003141static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3142 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3143}
3144
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003145bool SkPath::contains(SkScalar x, SkScalar y) const {
3146 bool isInverse = this->isInverseFillType();
3147 if (this->isEmpty()) {
3148 return isInverse;
3149 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003150
reed@google.com4db592c2013-10-30 17:39:43 +00003151 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003152 return isInverse;
3153 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003154
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003155 SkPath::Iter iter(*this, true);
3156 bool done = false;
3157 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003158 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 do {
3160 SkPoint pts[4];
3161 switch (iter.next(pts, false)) {
3162 case SkPath::kMove_Verb:
3163 case SkPath::kClose_Verb:
3164 break;
3165 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003166 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003167 break;
3168 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003169 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003171 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003172 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003173 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003175 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 break;
3177 case SkPath::kDone_Verb:
3178 done = true;
3179 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003180 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003181 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003182 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3183 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3184 if (evenOddFill) {
3185 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 }
caryclark9aacd902015-12-14 08:38:09 -08003187 if (w) {
3188 return !isInverse;
3189 }
3190 if (onCurveCount <= 1) {
3191 return SkToBool(onCurveCount) ^ isInverse;
3192 }
3193 if ((onCurveCount & 1) || evenOddFill) {
3194 return SkToBool(onCurveCount & 1) ^ isInverse;
3195 }
halcanary9d524f22016-03-29 09:03:52 -07003196 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003197 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3198 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003199 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003200 SkTDArray<SkVector> tangents;
3201 do {
3202 SkPoint pts[4];
3203 int oldCount = tangents.count();
3204 switch (iter.next(pts, false)) {
3205 case SkPath::kMove_Verb:
3206 case SkPath::kClose_Verb:
3207 break;
3208 case SkPath::kLine_Verb:
3209 tangent_line(pts, x, y, &tangents);
3210 break;
3211 case SkPath::kQuad_Verb:
3212 tangent_quad(pts, x, y, &tangents);
3213 break;
3214 case SkPath::kConic_Verb:
3215 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3216 break;
3217 case SkPath::kCubic_Verb:
3218 tangent_cubic(pts, x, y, &tangents);
3219 break;
3220 case SkPath::kDone_Verb:
3221 done = true;
3222 break;
3223 }
3224 if (tangents.count() > oldCount) {
3225 int last = tangents.count() - 1;
3226 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003227 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003228 tangents.remove(last);
3229 } else {
3230 for (int index = 0; index < last; ++index) {
3231 const SkVector& test = tangents[index];
3232 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003233 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3234 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003235 tangents.remove(last);
3236 tangents.removeShuffle(index);
3237 break;
3238 }
3239 }
3240 }
3241 }
3242 } while (!done);
3243 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003244}
fmalitaaa0df4e2015-12-01 09:13:23 -08003245
3246int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3247 SkScalar w, SkPoint pts[], int pow2) {
3248 const SkConic conic(p0, p1, p2, w);
3249 return conic.chopIntoQuadsPOW2(pts, pow2);
3250}
bsalomonedc743a2016-06-01 09:42:31 -07003251
3252bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3253 unsigned* start) {
3254 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3255 return false;
3256 }
3257 SkPath::RawIter iter(path);
3258 SkPoint verbPts[4];
3259 SkPath::Verb v;
3260 SkPoint rectPts[5];
3261 int rectPtCnt = 0;
3262 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3263 switch (v) {
3264 case SkPath::kMove_Verb:
3265 if (0 != rectPtCnt) {
3266 return false;
3267 }
3268 rectPts[0] = verbPts[0];
3269 ++rectPtCnt;
3270 break;
3271 case SkPath::kLine_Verb:
3272 if (5 == rectPtCnt) {
3273 return false;
3274 }
3275 rectPts[rectPtCnt] = verbPts[1];
3276 ++rectPtCnt;
3277 break;
3278 case SkPath::kClose_Verb:
3279 if (4 == rectPtCnt) {
3280 rectPts[4] = rectPts[0];
3281 rectPtCnt = 5;
3282 }
3283 break;
3284 default:
3285 return false;
3286 }
3287 }
3288 if (rectPtCnt < 5) {
3289 return false;
3290 }
3291 if (rectPts[0] != rectPts[4]) {
3292 return false;
3293 }
bsalomon057ae8a2016-07-24 05:37:26 -07003294 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3295 // and pts 1 and 2 the opposite vertical or horizontal edge).
3296 bool vec03IsVertical;
3297 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3298 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3299 // Make sure it has non-zero width and height
3300 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003301 return false;
3302 }
bsalomon057ae8a2016-07-24 05:37:26 -07003303 vec03IsVertical = true;
3304 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3305 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3306 // Make sure it has non-zero width and height
3307 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3308 return false;
3309 }
3310 vec03IsVertical = false;
3311 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003312 return false;
3313 }
bsalomon057ae8a2016-07-24 05:37:26 -07003314 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3315 // set if it is on the bottom edge.
3316 unsigned sortFlags =
3317 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3318 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3319 switch (sortFlags) {
3320 case 0b00:
3321 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3322 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3323 *start = 0;
3324 break;
3325 case 0b01:
3326 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3327 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3328 *start = 1;
3329 break;
3330 case 0b10:
3331 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3332 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3333 *start = 3;
3334 break;
3335 case 0b11:
3336 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3337 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3338 *start = 2;
3339 break;
bsalomonedc743a2016-06-01 09:42:31 -07003340 }
bsalomonedc743a2016-06-01 09:42:31 -07003341 return true;
3342}
bsalomon21af9ca2016-08-25 12:29:23 -07003343
3344void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3345 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3346 SkASSERT(!oval.isEmpty());
3347 SkASSERT(sweepAngle);
3348
3349 path->reset();
3350 path->setIsVolatile(true);
3351 path->setFillType(SkPath::kWinding_FillType);
3352 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3353 path->addOval(oval);
3354 return;
3355 }
3356 if (useCenter) {
3357 path->moveTo(oval.centerX(), oval.centerY());
3358 }
3359 // Arc to mods at 360 and drawArc is not supposed to.
3360 bool forceMoveTo = !useCenter;
3361 while (sweepAngle <= -360.f) {
3362 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3363 startAngle -= 180.f;
3364 path->arcTo(oval, startAngle, -180.f, false);
3365 startAngle -= 180.f;
3366 forceMoveTo = false;
3367 sweepAngle += 360.f;
3368 }
3369 while (sweepAngle >= 360.f) {
3370 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3371 startAngle += 180.f;
3372 path->arcTo(oval, startAngle, 180.f, false);
3373 startAngle += 180.f;
3374 forceMoveTo = false;
3375 sweepAngle -= 360.f;
3376 }
3377 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3378 if (useCenter) {
3379 path->close();
3380 }
3381}
Mike Reed0d7dac82017-02-02 17:45:56 -08003382
3383///////////////////////////////////////////////////////////////////////////////////////////////////
3384#include "SkNx.h"
3385
3386static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3387 SkScalar ts[2];
3388 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3389 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3390 SkASSERT(n >= 0 && n <= 2);
3391 for (int i = 0; i < n; ++i) {
3392 extremas[i] = SkEvalQuadAt(src, ts[i]);
3393 }
3394 extremas[n] = src[2];
3395 return n + 1;
3396}
3397
3398static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3399 SkConic conic(src[0], src[1], src[2], w);
3400 SkScalar ts[2];
3401 int n = conic.findXExtrema(ts);
3402 n += conic.findYExtrema(&ts[n]);
3403 SkASSERT(n >= 0 && n <= 2);
3404 for (int i = 0; i < n; ++i) {
3405 extremas[i] = conic.evalAt(ts[i]);
3406 }
3407 extremas[n] = src[2];
3408 return n + 1;
3409}
3410
3411static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3412 SkScalar ts[4];
3413 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3414 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3415 SkASSERT(n >= 0 && n <= 4);
3416 for (int i = 0; i < n; ++i) {
3417 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3418 }
3419 extremas[n] = src[3];
3420 return n + 1;
3421}
3422
Mike Reed8d3196b2017-02-03 11:34:13 -05003423SkRect SkPath::computeTightBounds() const {
3424 if (0 == this->countVerbs()) {
3425 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003426 }
3427
Mike Reed8d3196b2017-02-03 11:34:13 -05003428 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3429 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003430 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003431
Mike Reed0d7dac82017-02-02 17:45:56 -08003432 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3433 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003434 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003435
3436 // initial with the first MoveTo, so we don't have to check inside the switch
3437 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003438 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003439 for (;;) {
3440 int count = 0;
3441 switch (iter.next(pts)) {
3442 case SkPath::kMove_Verb:
3443 extremas[0] = pts[0];
3444 count = 1;
3445 break;
3446 case SkPath::kLine_Verb:
3447 extremas[0] = pts[1];
3448 count = 1;
3449 break;
3450 case SkPath::kQuad_Verb:
3451 count = compute_quad_extremas(pts, extremas);
3452 break;
3453 case SkPath::kConic_Verb:
3454 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3455 break;
3456 case SkPath::kCubic_Verb:
3457 count = compute_cubic_extremas(pts, extremas);
3458 break;
3459 case SkPath::kClose_Verb:
3460 break;
3461 case SkPath::kDone_Verb:
3462 goto DONE;
3463 }
3464 for (int i = 0; i < count; ++i) {
3465 Sk2s tmp = from_point(extremas[i]);
3466 min = Sk2s::Min(min, tmp);
3467 max = Sk2s::Max(max, tmp);
3468 }
3469 }
3470DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003471 SkRect bounds;
3472 min.store((SkPoint*)&bounds.fLeft);
3473 max.store((SkPoint*)&bounds.fRight);
3474 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003475}
Cary Clarkdf429f32017-11-08 11:44:31 -05003476
3477bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3478 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3479}
3480
3481bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3482 const SkPoint& p3, bool exact) {
3483 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3484 SkPointPriv::EqualsWithinTolerance(p2, p3);
3485}
3486
3487bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3488 const SkPoint& p3, const SkPoint& p4, bool exact) {
3489 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3490 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3491 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3492 SkPointPriv::EqualsWithinTolerance(p3, p4);
3493}