blob: 62a3cd6c552562358f7463f4c016ce33cbce8258 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
438 first,last,next direction state-machine:
439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000449bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400450 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 int corners = 0;
Cary Clark8540e112018-04-11 14:30:27 -0400452 SkPoint previous; // used to construct line from previous point
453 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
454 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
caryclark@google.com56f233a2012-11-19 13:06:06 +0000455 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400456 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
457 previous.set(0, 0);
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000458 int firstDirection = 0;
459 int lastDirection = 0;
460 int nextDirection = 0;
461 bool closedOrMoved = false;
Cary Clark8540e112018-04-11 14:30:27 -0400462 bool addedLine = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700464 bool insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400465 bool accumulatingRect = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000466 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000467 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700468 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
469 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 savePts = pts;
Cary Clark31608c02018-04-12 10:29:46 -0400472 pts = firstPt;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700474 insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400475 accumulatingRect = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 case kLine_Verb: {
Cary Clark8540e112018-04-11 14:30:27 -0400477 SkScalar left = previous.fX;
478 SkScalar top = previous.fY;
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 SkScalar right = pts->fX;
480 SkScalar bottom = pts->fY;
Cary Clark8540e112018-04-11 14:30:27 -0400481 if (accumulatingRect) {
482 lastPt = pts;
483 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 ++pts;
485 if (left != right && top != bottom) {
486 return false; // diagonal
487 }
Cary Clark8540e112018-04-11 14:30:27 -0400488 addedLine = true;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 if (left == right && top == bottom) {
490 break; // single point on side OK
491 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000492 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 if (0 == corners) {
494 firstDirection = nextDirection;
Cary Clark8540e112018-04-11 14:30:27 -0400495 previous = pts[-1];
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 corners = 1;
497 closedOrMoved = false;
498 break;
499 }
500 if (closedOrMoved) {
501 return false; // closed followed by a line
502 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000503 if (autoClose && nextDirection == firstDirection) {
504 break; // colinear with first
505 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 closedOrMoved = autoClose;
507 if (lastDirection != nextDirection) {
508 if (++corners > 4) {
509 return false; // too many direction changes
510 }
511 }
Cary Clark8540e112018-04-11 14:30:27 -0400512 previous = pts[-1];
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 if (lastDirection == nextDirection) {
514 break; // colinear segment
515 }
516 // Possible values for corners are 2, 3, and 4.
517 // When corners == 3, nextDirection opposes firstDirection.
518 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000519 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000520 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
521 if ((directionCycle ^ turn) != nextDirection) {
522 return false; // direction didn't follow cycle
523 }
524 break;
525 }
526 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000527 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000528 case kCubic_Verb:
529 return false; // quadratic, cubic not allowed
530 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700531 if (allowPartial && !autoClose && firstDirection) {
532 insertClose = true;
533 *currVerb -= 1; // try move again afterwards
534 goto addMissingClose;
535 }
Cary Clark8540e112018-04-11 14:30:27 -0400536 if (!addedLine) {
537 firstPt = pts;
538 accumulatingRect = true;
539 } else {
540 accumulatingRect = false;
541 }
542 previous = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000543 closedOrMoved = true;
544 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000545 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000546 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000547 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000548 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000550 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700551addMissingClose:
552 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000553 }
554 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400555 if (corners < 3 || corners > 4) {
556 return false;
557 }
558 SkPoint closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400559 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400560 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000561 if (!result) {
562 // check if we are just an incomplete rectangle, in which case we can
563 // return true, but not claim to be closed.
564 // e.g.
565 // 3 sided rectangle
566 // 4 sided but the last edge is not long enough to reach the start
567 //
Cary Clark8540e112018-04-11 14:30:27 -0400568 if (closeXY.fX && closeXY.fY) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000569 return false; // we're diagonal, abort (can we ever reach this?)
570 }
Cary Clark8540e112018-04-11 14:30:27 -0400571 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000572 // make sure the close-segment doesn't double-back on itself
Cary Clark8540e112018-04-11 14:30:27 -0400573 if (3 == corners || closeDirection == lastDirection) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000574 result = true;
575 autoClose = false; // we are not closed
576 }
577 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000578 if (savePts) {
579 *ptsPtr = savePts;
580 }
Cary Clark5c715182018-04-09 16:07:11 -0400581 if (result && rect) {
Cary Clark8540e112018-04-11 14:30:27 -0400582 ptrdiff_t count = lastPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400583 rect->set(firstPt, (int) count);
584 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000585 if (result && isClosed) {
586 *isClosed = autoClose;
587 }
588 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000589 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000590 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 return result;
592}
593
robertphillips4f662e62014-12-29 14:06:51 -0800594bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 SkDEBUGCODE(this->validate();)
596 int currVerb = 0;
597 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400598 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000599}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000600
caryclark95bc5f32015-04-08 08:34:15 -0700601bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602 SkDEBUGCODE(this->validate();)
603 int currVerb = 0;
604 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000605 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400606 SkRect testRects[2];
607 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 return false;
609 }
Cary Clark5c715182018-04-09 16:07:11 -0400610 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 if (testRects[0].contains(testRects[1])) {
612 if (rects) {
613 rects[0] = testRects[0];
614 rects[1] = testRects[1];
615 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000616 if (dirs) {
617 dirs[0] = testDirs[0];
618 dirs[1] = testDirs[1];
619 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000620 return true;
621 }
622 if (testRects[1].contains(testRects[0])) {
623 if (rects) {
624 rects[0] = testRects[1];
625 rects[1] = testRects[0];
626 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000627 if (dirs) {
628 dirs[0] = testDirs[1];
629 dirs[1] = testDirs[0];
630 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000631 return true;
632 }
633 }
634 return false;
635}
636
Mike Reed0c3137c2018-02-20 13:57:05 -0500637bool SkPath::isOval(SkRect* bounds) const {
638 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
639}
640
641bool SkPath::isRRect(SkRRect* rrect) const {
642 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
643}
644
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000645int SkPath::countPoints() const {
646 return fPathRef->countPoints();
647}
648
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000649int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkDEBUGCODE(this->validate();)
651
652 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000653 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800655 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657}
658
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000659SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
661 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000662 }
663 return SkPoint::Make(0, 0);
664}
665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666int SkPath::countVerbs() const {
667 return fPathRef->countVerbs();
668}
669
670static inline void copy_verbs_reverse(uint8_t* inorderDst,
671 const uint8_t* reversedSrc,
672 int count) {
673 for (int i = 0; i < count; ++i) {
674 inorderDst[i] = reversedSrc[~i];
675 }
676}
677
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000678int SkPath::getVerbs(uint8_t dst[], int max) const {
679 SkDEBUGCODE(this->validate();)
680
681 SkASSERT(max >= 0);
682 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 int count = SkMin32(max, fPathRef->countVerbs());
684 copy_verbs_reverse(dst, fPathRef->verbs(), count);
685 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000686}
687
reed@google.com294dd7b2011-10-11 11:58:32 +0000688bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 SkDEBUGCODE(this->validate();)
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000692 if (count > 0) {
693 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000698 if (lastPt) {
699 lastPt->set(0, 0);
700 }
701 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000702}
703
caryclarkaec25102015-04-29 08:28:30 -0700704void SkPath::setPt(int index, SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
707 int count = fPathRef->countPoints();
708 if (count <= index) {
709 return;
710 } else {
711 SkPathRef::Editor ed(&fPathRef);
712 ed.atPoint(index)->set(x, y);
713 }
714}
715
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716void SkPath::setLastPt(SkScalar x, SkScalar y) {
717 SkDEBUGCODE(this->validate();)
718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 if (count == 0) {
721 this->moveTo(x, y);
722 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
724 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 }
726}
727
reed@google.com04863fa2011-05-15 04:08:24 +0000728void SkPath::setConvexity(Convexity c) {
729 if (fConvexity != c) {
730 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000731 }
732}
733
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734//////////////////////////////////////////////////////////////////////////////
735// Construction methods
736
reed026beb52015-06-10 14:23:15 -0700737#define DIRTY_AFTER_EDIT \
738 do { \
739 fConvexity = kUnknown_Convexity; \
740 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000741 } while (0)
742
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743void SkPath::incReserve(U16CPU inc) {
744 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 SkDEBUGCODE(this->validate();)
747}
748
749void SkPath::moveTo(SkScalar x, SkScalar y) {
750 SkDEBUGCODE(this->validate();)
751
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000752 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000754 // remember our index
755 fLastMoveToIndex = fPathRef->countPoints();
756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000757 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700758
759 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760}
761
762void SkPath::rMoveTo(SkScalar x, SkScalar y) {
763 SkPoint pt;
764 this->getLastPt(&pt);
765 this->moveTo(pt.fX + x, pt.fY + y);
766}
767
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000768void SkPath::injectMoveToIfNeeded() {
769 if (fLastMoveToIndex < 0) {
770 SkScalar x, y;
771 if (fPathRef->countVerbs() == 0) {
772 x = y = 0;
773 } else {
774 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
775 x = pt.fX;
776 y = pt.fY;
777 }
778 this->moveTo(x, y);
779 }
780}
781
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782void SkPath::lineTo(SkScalar x, SkScalar y) {
783 SkDEBUGCODE(this->validate();)
784
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000785 this->injectMoveToIfNeeded();
786
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000787 SkPathRef::Editor ed(&fPathRef);
788 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789
reed@google.comb54455e2011-05-16 14:16:04 +0000790 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791}
792
793void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000794 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 SkPoint pt;
796 this->getLastPt(&pt);
797 this->lineTo(pt.fX + x, pt.fY + y);
798}
799
800void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
801 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000802
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000803 this->injectMoveToIfNeeded();
804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000805 SkPathRef::Editor ed(&fPathRef);
806 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 pts[0].set(x1, y1);
808 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000809
reed@google.comb54455e2011-05-16 14:16:04 +0000810 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811}
812
813void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000814 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 SkPoint pt;
816 this->getLastPt(&pt);
817 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
818}
819
reed@google.com277c3f82013-05-31 15:17:50 +0000820void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
821 SkScalar w) {
822 // check for <= 0 or NaN with this test
823 if (!(w > 0)) {
824 this->lineTo(x2, y2);
825 } else if (!SkScalarIsFinite(w)) {
826 this->lineTo(x1, y1);
827 this->lineTo(x2, y2);
828 } else if (SK_Scalar1 == w) {
829 this->quadTo(x1, y1, x2, y2);
830 } else {
831 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000832
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000833 this->injectMoveToIfNeeded();
834
reed@google.com277c3f82013-05-31 15:17:50 +0000835 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000836 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000837 pts[0].set(x1, y1);
838 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000839
reed@google.com277c3f82013-05-31 15:17:50 +0000840 DIRTY_AFTER_EDIT;
841 }
842}
843
844void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
845 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000846 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000847 SkPoint pt;
848 this->getLastPt(&pt);
849 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
850}
851
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
853 SkScalar x3, SkScalar y3) {
854 SkDEBUGCODE(this->validate();)
855
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000856 this->injectMoveToIfNeeded();
857
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000858 SkPathRef::Editor ed(&fPathRef);
859 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 pts[0].set(x1, y1);
861 pts[1].set(x2, y2);
862 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863
reed@google.comb54455e2011-05-16 14:16:04 +0000864 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865}
866
867void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
868 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000869 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 SkPoint pt;
871 this->getLastPt(&pt);
872 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
873 pt.fX + x3, pt.fY + y3);
874}
875
876void SkPath::close() {
877 SkDEBUGCODE(this->validate();)
878
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 case kLine_Verb:
883 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000884 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000885 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000886 case kMove_Verb: {
887 SkPathRef::Editor ed(&fPathRef);
888 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000890 }
reed@google.com277c3f82013-05-31 15:17:50 +0000891 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000892 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000893 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000894 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000895 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000896 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 }
898 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000899
900 // signal that we need a moveTo to follow us (unless we're done)
901#if 0
902 if (fLastMoveToIndex >= 0) {
903 fLastMoveToIndex = ~fLastMoveToIndex;
904 }
905#else
906 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
907#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908}
909
910///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000911
fmalitac08d53e2015-11-17 09:53:29 -0800912namespace {
913
914template <unsigned N>
915class PointIterator {
916public:
917 PointIterator(SkPath::Direction dir, unsigned startIndex)
918 : fCurrent(startIndex % N)
919 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
920
921 const SkPoint& current() const {
922 SkASSERT(fCurrent < N);
923 return fPts[fCurrent];
924 }
925
926 const SkPoint& next() {
927 fCurrent = (fCurrent + fAdvance) % N;
928 return this->current();
929 }
930
931protected:
932 SkPoint fPts[N];
933
934private:
935 unsigned fCurrent;
936 unsigned fAdvance;
937};
938
939class RectPointIterator : public PointIterator<4> {
940public:
941 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
942 : PointIterator(dir, startIndex) {
943
944 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
945 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
946 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
947 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
948 }
949};
950
951class OvalPointIterator : public PointIterator<4> {
952public:
953 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
954 : PointIterator(dir, startIndex) {
955
956 const SkScalar cx = oval.centerX();
957 const SkScalar cy = oval.centerY();
958
959 fPts[0] = SkPoint::Make(cx, oval.fTop);
960 fPts[1] = SkPoint::Make(oval.fRight, cy);
961 fPts[2] = SkPoint::Make(cx, oval.fBottom);
962 fPts[3] = SkPoint::Make(oval.fLeft, cy);
963 }
964};
965
966class RRectPointIterator : public PointIterator<8> {
967public:
968 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
969 : PointIterator(dir, startIndex) {
970
971 const SkRect& bounds = rrect.getBounds();
972 const SkScalar L = bounds.fLeft;
973 const SkScalar T = bounds.fTop;
974 const SkScalar R = bounds.fRight;
975 const SkScalar B = bounds.fBottom;
976
977 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
978 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
979 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
980 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
981 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
982 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
983 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
984 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
985 }
986};
987
988} // anonymous namespace
989
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000990static void assert_known_direction(int dir) {
991 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
992}
993
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800995 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996}
997
998void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
999 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001000 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
1001}
1002
1003void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001004 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001005 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001006 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001007 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001008 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011
fmalitac08d53e2015-11-17 09:53:29 -08001012 const int kVerbs = 5; // moveTo + 3x lineTo + close
1013 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014
fmalitac08d53e2015-11-17 09:53:29 -08001015 RectPointIterator iter(rect, dir, startIndex);
1016
1017 this->moveTo(iter.current());
1018 this->lineTo(iter.next());
1019 this->lineTo(iter.next());
1020 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001022
1023 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001024}
1025
reed@google.com744faba2012-05-29 19:54:52 +00001026void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1027 SkDEBUGCODE(this->validate();)
1028 if (count <= 0) {
1029 return;
1030 }
1031
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001032 fLastMoveToIndex = fPathRef->countPoints();
1033
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001034 // +close makes room for the extra kClose_Verb
1035 SkPathRef::Editor ed(&fPathRef, count+close, count);
1036
1037 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001038 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001039 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1040 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001041 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042
reed@google.com744faba2012-05-29 19:54:52 +00001043 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001044 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001045 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001046 }
1047
reed@google.com744faba2012-05-29 19:54:52 +00001048 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001049 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001050}
1051
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052#include "SkGeometry.h"
1053
reedf90ea012015-01-29 12:03:58 -08001054static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1055 SkPoint* pt) {
1056 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057 // Chrome uses this path to move into and out of ovals. If not
1058 // treated as a special case the moves can distort the oval's
1059 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001060 pt->set(oval.fRight, oval.centerY());
1061 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062 } else if (0 == oval.width() && 0 == oval.height()) {
1063 // Chrome will sometimes create 0 radius round rects. Having degenerate
1064 // quad segments in the path prevents the path from being recognized as
1065 // a rect.
1066 // TODO: optimizing the case where only one of width or height is zero
1067 // should also be considered. This case, however, doesn't seem to be
1068 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001069 pt->set(oval.fRight, oval.fTop);
1070 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071 }
reedf90ea012015-01-29 12:03:58 -08001072 return false;
1073}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074
reedd5d27d92015-02-09 13:54:43 -08001075// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1076//
1077static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1078 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1079 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1080 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081
1082 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001083 loss in radians-conversion and/or sin/cos, we may end up with coincident
1084 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1085 of drawing a nearly complete circle (good).
1086 e.g. canvas.drawArc(0, 359.99, ...)
1087 -vs- canvas.drawArc(0, 359.9, ...)
1088 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 */
reedd5d27d92015-02-09 13:54:43 -08001090 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091 SkScalar sw = SkScalarAbs(sweepAngle);
1092 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1093 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1094 // make a guess at a tiny angle (in radians) to tweak by
1095 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1096 // not sure how much will be enough, so we use a loop
1097 do {
1098 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001099 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1100 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001101 }
1102 }
reedd5d27d92015-02-09 13:54:43 -08001103 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1104}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001105
reed9e779d42015-02-17 11:43:14 -08001106/**
1107 * If this returns 0, then the caller should just line-to the singlePt, else it should
1108 * ignore singlePt and append the specified number of conics.
1109 */
reedd5d27d92015-02-09 13:54:43 -08001110static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001111 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1112 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001113 SkMatrix matrix;
1114
1115 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1116 matrix.postTranslate(oval.centerX(), oval.centerY());
1117
reed9e779d42015-02-17 11:43:14 -08001118 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1119 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001120 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001121 }
1122 return count;
reedd5d27d92015-02-09 13:54:43 -08001123}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001124
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001127 SkRRect rrect;
1128 rrect.setRectRadii(rect, (const SkVector*) radii);
1129 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130}
1131
reed@google.com4ed0fb72012-12-12 20:48:18 +00001132void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001133 // legacy start indices: 6 (CW) and 7(CCW)
1134 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1135}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001136
fmalitac08d53e2015-11-17 09:53:29 -08001137void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1138 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001139
caryclarkda707bf2015-11-19 14:47:43 -08001140 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001141 const SkRect& bounds = rrect.getBounds();
1142
Brian Salomon0a241ce2017-12-15 11:31:05 -05001143 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001144 // degenerate(rect) => radii points are collapsing
1145 this->addRect(bounds, dir, (startIndex + 1) / 2);
1146 } else if (rrect.isOval()) {
1147 // degenerate(oval) => line points are collapsing
1148 this->addOval(bounds, dir, startIndex / 2);
1149 } else {
1150 fFirstDirection = this->hasOnlyMoveTos() ?
1151 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1152
1153 SkAutoPathBoundsUpdate apbu(this, bounds);
1154 SkAutoDisableDirectionCheck addc(this);
1155
1156 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1157 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1158 const SkScalar weight = SK_ScalarRoot2Over2;
1159
1160 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1161 const int kVerbs = startsWithConic
1162 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1163 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1164 this->incReserve(kVerbs);
1165
1166 RRectPointIterator rrectIter(rrect, dir, startIndex);
1167 // Corner iterator indices follow the collapsed radii model,
1168 // adjusted such that the start pt is "behind" the radii start pt.
1169 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1170 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1171
1172 this->moveTo(rrectIter.current());
1173 if (startsWithConic) {
1174 for (unsigned i = 0; i < 3; ++i) {
1175 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1176 this->lineTo(rrectIter.next());
1177 }
1178 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1179 // final lineTo handled by close().
1180 } else {
1181 for (unsigned i = 0; i < 4; ++i) {
1182 this->lineTo(rrectIter.next());
1183 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1184 }
1185 }
1186 this->close();
1187
caryclarkda707bf2015-11-19 14:47:43 -08001188 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001189 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001190
fmalitac08d53e2015-11-17 09:53:29 -08001191 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1192 }
1193
1194 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001195}
1196
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001198 int count = fPathRef->countVerbs();
1199 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1200 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001201 if (*verbs == kLine_Verb ||
1202 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001203 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001204 *verbs == kCubic_Verb) {
1205 return false;
1206 }
1207 ++verbs;
1208 }
1209 return true;
1210}
1211
Brian Osmana2318572017-07-10 15:09:26 -04001212bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1213 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001214 if (count < 2) {
1215 return true;
1216 }
Brian Osmana2318572017-07-10 15:09:26 -04001217 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001218 const SkPoint& first = *pts;
1219 for (int index = 1; index < count; ++index) {
1220 if (first != pts[index]) {
1221 return false;
1222 }
1223 }
1224 return true;
1225}
1226
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001227void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1228 Direction dir) {
1229 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001230
humper@google.com75e3ca12013-04-08 21:44:11 +00001231 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001232 return;
1233 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001234
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001235 SkRRect rrect;
1236 rrect.setRectXY(rect, rx, ry);
1237 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001238}
1239
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001241 // legacy start index: 1
1242 this->addOval(oval, dir, 1);
1243}
1244
1245void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001246 assert_known_direction(dir);
1247
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001248 /* If addOval() is called after previous moveTo(),
1249 this path is still marked as an oval. This is used to
1250 fit into WebKit's calling sequences.
1251 We can't simply check isEmpty() in this case, as additional
1252 moveTo() would mark the path non empty.
1253 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001254 bool isOval = hasOnlyMoveTos();
1255 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001256 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 } else {
reed026beb52015-06-10 14:23:15 -07001258 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001260
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001261 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 SkAutoPathBoundsUpdate apbu(this, oval);
1263
fmalitac08d53e2015-11-17 09:53:29 -08001264 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1265 const int kVerbs = 6; // moveTo + 4x conicTo + close
1266 this->incReserve(kVerbs);
1267
1268 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1269 // The corner iterator pts are tracking "behind" the oval/radii pts.
1270 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001271 const SkScalar weight = SK_ScalarRoot2Over2;
1272
fmalitac08d53e2015-11-17 09:53:29 -08001273 this->moveTo(ovalIter.current());
1274 for (unsigned i = 0; i < 4; ++i) {
1275 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001276 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278
fmalitac08d53e2015-11-17 09:53:29 -08001279 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1280
robertphillips@google.com466310d2013-12-03 16:43:54 +00001281 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282
bsalomon78d58d12016-05-27 09:17:04 -07001283 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001284}
1285
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1287 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001288 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 }
1290}
1291
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1293 bool forceMoveTo) {
1294 if (oval.width() < 0 || oval.height() < 0) {
1295 return;
1296 }
1297
reedf90ea012015-01-29 12:03:58 -08001298 if (fPathRef->countVerbs() == 0) {
1299 forceMoveTo = true;
1300 }
1301
1302 SkPoint lonePt;
1303 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1304 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1305 return;
1306 }
1307
reedd5d27d92015-02-09 13:54:43 -08001308 SkVector startV, stopV;
1309 SkRotationDirection dir;
1310 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1311
reed9e779d42015-02-17 11:43:14 -08001312 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001313
1314 // At this point, we know that the arc is not a lone point, but startV == stopV
1315 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1316 // cannot handle it.
1317 if (startV == stopV) {
1318 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1319 SkScalar radiusX = oval.width() / 2;
1320 SkScalar radiusY = oval.height() / 2;
1321 // We cannot use SkScalarSinCos function in the next line because
1322 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1323 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001324 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001325 // make sin(endAngle) to be 0 which will then draw a dot.
1326 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1327 oval.centerY() + radiusY * sk_float_sin(endAngle));
1328 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1329 return;
1330 }
1331
reedd5d27d92015-02-09 13:54:43 -08001332 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001333 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001334 if (count) {
1335 this->incReserve(count * 2 + 1);
1336 const SkPoint& pt = conics[0].fPts[0];
1337 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1338 for (int i = 0; i < count; ++i) {
1339 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1340 }
reed9e779d42015-02-17 11:43:14 -08001341 } else {
1342 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001343 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344}
1345
caryclark55d49052016-01-23 05:07:04 -08001346// This converts the SVG arc to conics.
1347// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1348// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1349// See also SVG implementation notes:
1350// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1351// Note that arcSweep bool value is flipped from the original implementation.
1352void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1353 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001354 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001355 SkPoint srcPts[2];
1356 this->getLastPt(&srcPts[0]);
1357 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1358 // joining the endpoints.
1359 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1360 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001361 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001362 return;
1363 }
1364 // If the current point and target point for the arc are identical, it should be treated as a
1365 // zero length path. This ensures continuity in animations.
1366 srcPts[1].set(x, y);
1367 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001368 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001369 return;
1370 }
1371 rx = SkScalarAbs(rx);
1372 ry = SkScalarAbs(ry);
1373 SkVector midPointDistance = srcPts[0] - srcPts[1];
1374 midPointDistance *= 0.5f;
1375
1376 SkMatrix pointTransform;
1377 pointTransform.setRotate(-angle);
1378
1379 SkPoint transformedMidPoint;
1380 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1381 SkScalar squareRx = rx * rx;
1382 SkScalar squareRy = ry * ry;
1383 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1384 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1385
1386 // Check if the radii are big enough to draw the arc, scale radii if not.
1387 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1388 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1389 if (radiiScale > 1) {
1390 radiiScale = SkScalarSqrt(radiiScale);
1391 rx *= radiiScale;
1392 ry *= radiiScale;
1393 }
1394
1395 pointTransform.setScale(1 / rx, 1 / ry);
1396 pointTransform.preRotate(-angle);
1397
1398 SkPoint unitPts[2];
1399 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1400 SkVector delta = unitPts[1] - unitPts[0];
1401
1402 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1403 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1404
1405 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1406 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1407 scaleFactor = -scaleFactor;
1408 }
1409 delta.scale(scaleFactor);
1410 SkPoint centerPoint = unitPts[0] + unitPts[1];
1411 centerPoint *= 0.5f;
1412 centerPoint.offset(-delta.fY, delta.fX);
1413 unitPts[0] -= centerPoint;
1414 unitPts[1] -= centerPoint;
1415 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1416 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1417 SkScalar thetaArc = theta2 - theta1;
1418 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1419 thetaArc += SK_ScalarPI * 2;
1420 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1421 thetaArc -= SK_ScalarPI * 2;
1422 }
1423 pointTransform.setRotate(angle);
1424 pointTransform.preScale(rx, ry);
1425
Cary Clark1acd3c72017-12-08 11:37:01 -05001426#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001427 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001428#else
1429 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1430 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1431#endif
caryclark55d49052016-01-23 05:07:04 -08001432 SkScalar thetaWidth = thetaArc / segments;
1433 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1434 if (!SkScalarIsFinite(t)) {
1435 return;
1436 }
1437 SkScalar startTheta = theta1;
1438 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001439#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1440 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1441 return scalar == SkScalarFloorToScalar(scalar);
1442 };
1443 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1444 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1445 scalar_is_integer(x) && scalar_is_integer(y);
1446#endif
caryclark55d49052016-01-23 05:07:04 -08001447 for (int i = 0; i < segments; ++i) {
1448 SkScalar endTheta = startTheta + thetaWidth;
1449 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1450
1451 unitPts[1].set(cosEndTheta, sinEndTheta);
1452 unitPts[1] += centerPoint;
1453 unitPts[0] = unitPts[1];
1454 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1455 SkPoint mapped[2];
1456 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001457 /*
1458 Computing the arc width introduces rounding errors that cause arcs to start
1459 outside their marks. A round rect may lose convexity as a result. If the input
1460 values are on integers, place the conic on integers as well.
1461 */
1462#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1463 if (expectIntegers) {
1464 SkScalar* mappedScalars = &mapped[0].fX;
1465 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1466 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1467 }
1468 }
1469#endif
caryclark55d49052016-01-23 05:07:04 -08001470 this->conicTo(mapped[0], mapped[1], w);
1471 startTheta = endTheta;
1472 }
1473}
1474
1475void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1476 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1477 SkPoint currentPoint;
1478 this->getLastPt(&currentPoint);
1479 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1480}
1481
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001482void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 if (oval.isEmpty() || 0 == sweepAngle) {
1484 return;
1485 }
1486
1487 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1488
1489 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001490 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1491 // See SkPath::addOval() docs.
1492 SkScalar startOver90 = startAngle / 90.f;
1493 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1494 SkScalar error = startOver90 - startOver90I;
1495 if (SkScalarNearlyEqual(error, 0)) {
1496 // Index 1 is at startAngle == 0.
1497 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1498 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1499 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1500 (unsigned) startIndex);
1501 return;
1502 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 }
bsalomon1978ce22016-05-31 10:42:16 -07001504 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505}
1506
1507/*
1508 Need to handle the case when the angle is sharp, and our computed end-points
1509 for the arc go behind pt1 and/or p2...
1510*/
reedc7789042015-01-29 12:59:11 -08001511void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001512 if (radius == 0) {
1513 this->lineTo(x1, y1);
1514 return;
1515 }
1516
1517 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001518
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 // need to know our prev pt so we can construct tangent vectors
1520 {
1521 SkPoint start;
1522 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001523 // Handle degenerate cases by adding a line to the first point and
1524 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 before.setNormalize(x1 - start.fX, y1 - start.fY);
1526 after.setNormalize(x2 - x1, y2 - y1);
1527 }
reed@google.comabf15c12011-01-18 20:35:51 +00001528
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 SkScalar cosh = SkPoint::DotProduct(before, after);
1530 SkScalar sinh = SkPoint::CrossProduct(before, after);
1531
1532 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001533 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 return;
1535 }
reed@google.comabf15c12011-01-18 20:35:51 +00001536
Mike Reeda99b6ce2017-02-04 11:04:26 -05001537 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538
Mike Reeda99b6ce2017-02-04 11:04:26 -05001539 SkScalar xx = x1 - dist * before.fX;
1540 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001541 after.setLength(dist);
1542 this->lineTo(xx, yy);
1543 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1544 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545}
1546
1547///////////////////////////////////////////////////////////////////////////////
1548
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001549void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 SkMatrix matrix;
1551
1552 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001553 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554}
1555
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001556void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001559 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 SkPoint pts[4];
1561 Verb verb;
1562
Cary Clark9480d822017-10-19 18:01:13 -04001563 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001564 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 while ((verb = iter.next(pts)) != kDone_Verb) {
1566 switch (verb) {
1567 case kMove_Verb:
1568 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001569 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1570 injectMoveToIfNeeded(); // In case last contour is closed
1571 this->lineTo(pts[0]);
1572 } else {
1573 this->moveTo(pts[0]);
1574 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575 break;
1576 case kLine_Verb:
1577 proc(matrix, &pts[1], &pts[1], 1);
1578 this->lineTo(pts[1]);
1579 break;
1580 case kQuad_Verb:
1581 proc(matrix, &pts[1], &pts[1], 2);
1582 this->quadTo(pts[1], pts[2]);
1583 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001584 case kConic_Verb:
1585 proc(matrix, &pts[1], &pts[1], 2);
1586 this->conicTo(pts[1], pts[2], iter.conicWeight());
1587 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 case kCubic_Verb:
1589 proc(matrix, &pts[1], &pts[1], 3);
1590 this->cubicTo(pts[1], pts[2], pts[3]);
1591 break;
1592 case kClose_Verb:
1593 this->close();
1594 break;
1595 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001596 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001598 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 }
1600}
1601
1602///////////////////////////////////////////////////////////////////////////////
1603
reed@google.com277c3f82013-05-31 15:17:50 +00001604static int pts_in_verb(unsigned verb) {
1605 static const uint8_t gPtsInVerb[] = {
1606 1, // kMove
1607 1, // kLine
1608 2, // kQuad
1609 2, // kConic
1610 3, // kCubic
1611 0, // kClose
1612 0 // kDone
1613 };
1614
1615 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1616 return gPtsInVerb[verb];
1617}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619// ignore the last point of the 1st contour
1620void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001621 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1622 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 return;
1624 }
caryclark51c56782016-11-07 05:09:28 -08001625 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1626 SkASSERT(verbsEnd[0] == kMove_Verb);
1627 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1628 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629
caryclark51c56782016-11-07 05:09:28 -08001630 while (verbs < verbsEnd) {
1631 uint8_t v = *verbs++;
1632 pts -= pts_in_verb(v);
1633 switch (v) {
1634 case kMove_Verb:
1635 // if the path has multiple contours, stop after reversing the last
1636 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001638 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 break;
1640 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001641 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001643 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001644 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001645 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001647 this->cubicTo(pts[2], pts[1], pts[0]);
1648 break;
1649 case kClose_Verb:
1650 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 break;
1652 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001653 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 break;
1655 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656 }
1657}
1658
reed@google.com63d73742012-01-10 15:33:12 +00001659void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 const SkPoint* pts = src.fPathRef->pointsEnd();
1663 // we will iterator through src's verbs backwards
1664 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1665 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001666 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001667
1668 bool needMove = true;
1669 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001670 while (verbs < verbsEnd) {
1671 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001672 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001673
1674 if (needMove) {
1675 --pts;
1676 this->moveTo(pts->fX, pts->fY);
1677 needMove = false;
1678 }
1679 pts -= n;
1680 switch (v) {
1681 case kMove_Verb:
1682 if (needClose) {
1683 this->close();
1684 needClose = false;
1685 }
1686 needMove = true;
1687 pts += 1; // so we see the point in "if (needMove)" above
1688 break;
1689 case kLine_Verb:
1690 this->lineTo(pts[0]);
1691 break;
1692 case kQuad_Verb:
1693 this->quadTo(pts[1], pts[0]);
1694 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001695 case kConic_Verb:
1696 this->conicTo(pts[1], pts[0], *--conicWeights);
1697 break;
reed@google.com63d73742012-01-10 15:33:12 +00001698 case kCubic_Verb:
1699 this->cubicTo(pts[2], pts[1], pts[0]);
1700 break;
1701 case kClose_Verb:
1702 needClose = true;
1703 break;
1704 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001705 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001706 }
1707 }
1708}
1709
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710///////////////////////////////////////////////////////////////////////////////
1711
1712void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1713 SkMatrix matrix;
1714
1715 matrix.setTranslate(dx, dy);
1716 this->transform(matrix, dst);
1717}
1718
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1720 int level = 2) {
1721 if (--level >= 0) {
1722 SkPoint tmp[7];
1723
1724 SkChopCubicAtHalf(pts, tmp);
1725 subdivide_cubic_to(path, &tmp[0], level);
1726 subdivide_cubic_to(path, &tmp[3], level);
1727 } else {
1728 path->cubicTo(pts[1], pts[2], pts[3]);
1729 }
1730}
1731
1732void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1733 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001734 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735 dst = (SkPath*)this;
1736 }
1737
tomhudson@google.com8d430182011-06-06 19:11:19 +00001738 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 SkPath tmp;
1740 tmp.fFillType = fFillType;
1741
1742 SkPath::Iter iter(*this, false);
1743 SkPoint pts[4];
1744 SkPath::Verb verb;
1745
reed@google.com4a3b7142012-05-16 17:16:46 +00001746 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 switch (verb) {
1748 case kMove_Verb:
1749 tmp.moveTo(pts[0]);
1750 break;
1751 case kLine_Verb:
1752 tmp.lineTo(pts[1]);
1753 break;
1754 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001755 // promote the quad to a conic
1756 tmp.conicTo(pts[1], pts[2],
1757 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001759 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001760 tmp.conicTo(pts[1], pts[2],
1761 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001762 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 case kCubic_Verb:
1764 subdivide_cubic_to(&tmp, pts);
1765 break;
1766 case kClose_Verb:
1767 tmp.close();
1768 break;
1769 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001770 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 break;
1772 }
1773 }
1774
1775 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001776 SkPathRef::Editor ed(&dst->fPathRef);
1777 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001778 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001780 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1781
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001784 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001785 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001787
reed026beb52015-06-10 14:23:15 -07001788 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1789 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001790 } else {
1791 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001792 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1793 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001794 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001795 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1796 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001797 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001798 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001799 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001800 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001801 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001802 }
1803 }
1804
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 SkDEBUGCODE(dst->validate();)
1806 }
1807}
1808
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809///////////////////////////////////////////////////////////////////////////////
1810///////////////////////////////////////////////////////////////////////////////
1811
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001813 kEmptyContour_SegmentState, // The current contour is empty. We may be
1814 // starting processing or we may have just
1815 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1817 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1818 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819};
1820
1821SkPath::Iter::Iter() {
1822#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001823 fPts = nullptr;
1824 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001827 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828#endif
1829 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001830 fVerbs = nullptr;
1831 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 fNeedClose = false;
1833}
1834
1835SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1836 this->setPath(path, forceClose);
1837}
1838
1839void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001840 fPts = path.fPathRef->points();
1841 fVerbs = path.fPathRef->verbs();
1842 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001843 fConicWeights = path.fPathRef->conicWeights();
1844 if (fConicWeights) {
1845 fConicWeights -= 1; // begin one behind
1846 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001848 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 fForceClose = SkToU8(forceClose);
1850 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001851 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852}
1853
1854bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001855 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 return false;
1857 }
1858 if (fForceClose) {
1859 return true;
1860 }
1861
1862 const uint8_t* verbs = fVerbs;
1863 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001864
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001865 if (kMove_Verb == *(verbs - 1)) {
1866 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 }
1868
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 while (verbs > stop) {
1870 // verbs points one beyond the current verb, decrement first.
1871 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872 if (kMove_Verb == v) {
1873 break;
1874 }
1875 if (kClose_Verb == v) {
1876 return true;
1877 }
1878 }
1879 return false;
1880}
1881
1882SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001883 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001885 // A special case: if both points are NaN, SkPoint::operation== returns
1886 // false, but the iterator expects that they are treated as the same.
1887 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001888 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1889 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001890 return kClose_Verb;
1891 }
1892
reed@google.com9e25dbf2012-05-15 17:05:38 +00001893 pts[0] = fLastPt;
1894 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895 fLastPt = fMoveTo;
1896 fCloseLine = true;
1897 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001898 } else {
1899 pts[0] = fMoveTo;
1900 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902}
1903
reed@google.com9e25dbf2012-05-15 17:05:38 +00001904const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 if (fSegmentState == kAfterMove_SegmentState) {
1906 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1911 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001912 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914}
1915
caryclarke8c56662015-07-14 11:19:26 -07001916void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 // We need to step over anything that will not move the current draw point
1918 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001919 const uint8_t* lastMoveVerb = nullptr;
1920 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001921 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 SkPoint lastPt = fLastPt;
1923 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 switch (verb) {
1926 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 // Keep a record of this most recent move
1928 lastMoveVerb = fVerbs;
1929 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001930 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001931 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 fPts++;
1934 break;
1935
1936 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001937 // A close when we are in a segment is always valid except when it
1938 // follows a move which follows a segment.
1939 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 return;
1941 }
1942 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001943 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 break;
1945
1946 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001947 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 if (lastMoveVerb) {
1949 fVerbs = lastMoveVerb;
1950 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001951 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 return;
1953 }
1954 return;
1955 }
1956 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001957 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 fPts++;
1959 break;
1960
reed@google.com277c3f82013-05-31 15:17:50 +00001961 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001963 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 if (lastMoveVerb) {
1965 fVerbs = lastMoveVerb;
1966 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001967 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 return;
1969 }
1970 return;
1971 }
1972 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001973 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001975 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 break;
1977
1978 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001979 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 if (lastMoveVerb) {
1981 fVerbs = lastMoveVerb;
1982 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001983 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 return;
1985 }
1986 return;
1987 }
1988 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001989 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001990 fPts += 3;
1991 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001992
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001994 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995 }
1996 }
1997}
1998
reed@google.com4a3b7142012-05-16 17:16:46 +00001999SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002000 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002001
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 // Close the curve if requested and if there is some curve to close
2004 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002005 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006 return kLine_Verb;
2007 }
2008 fNeedClose = false;
2009 return kClose_Verb;
2010 }
2011 return kDone_Verb;
2012 }
2013
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002014 // fVerbs is one beyond the current verb, decrement first
2015 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002016 const SkPoint* SK_RESTRICT srcPts = fPts;
2017 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018
2019 switch (verb) {
2020 case kMove_Verb:
2021 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023 verb = this->autoClose(pts);
2024 if (verb == kClose_Verb) {
2025 fNeedClose = false;
2026 }
2027 return (Verb)verb;
2028 }
2029 if (fVerbs == fVerbStop) { // might be a trailing moveto
2030 return kDone_Verb;
2031 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002032 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002033 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002035 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002036 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 fNeedClose = fForceClose;
2038 break;
2039 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002040 pts[0] = this->cons_moveTo();
2041 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 fLastPt = srcPts[0];
2043 fCloseLine = false;
2044 srcPts += 1;
2045 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002046 case kConic_Verb:
2047 fConicWeights += 1;
2048 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002050 pts[0] = this->cons_moveTo();
2051 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 fLastPt = srcPts[1];
2053 srcPts += 2;
2054 break;
2055 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002056 pts[0] = this->cons_moveTo();
2057 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 fLastPt = srcPts[2];
2059 srcPts += 3;
2060 break;
2061 case kClose_Verb:
2062 verb = this->autoClose(pts);
2063 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002064 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 } else {
2066 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002067 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002069 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 break;
2071 }
2072 fPts = srcPts;
2073 return (Verb)verb;
2074}
2075
2076///////////////////////////////////////////////////////////////////////////////
2077
Ben Wagner4d1955c2017-03-10 13:08:15 -05002078#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002079#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002080#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002081
reed@google.com51bbe752013-01-17 22:07:50 +00002082static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002083 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002084 str->append(label);
2085 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002086
reed@google.com51bbe752013-01-17 22:07:50 +00002087 const SkScalar* values = &pts[0].fX;
2088 count *= 2;
2089
2090 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002091 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002092 if (i < count - 1) {
2093 str->append(", ");
2094 }
2095 }
Mike Reed405b9d22018-01-09 15:11:08 -05002096 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002097 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002098 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002099 }
caryclark08fa28c2014-10-23 13:08:56 -07002100 str->append(");");
reede05fed02014-12-15 07:59:53 -08002101 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002102 str->append(" // ");
2103 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002104 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002105 if (i < count - 1) {
2106 str->append(", ");
2107 }
2108 }
2109 if (conicWeight >= 0) {
2110 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002111 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002112 }
2113 }
2114 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002115}
2116
caryclarke9562592014-09-15 09:26:09 -07002117void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002118 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 Iter iter(*this, forceClose);
2120 SkPoint pts[4];
2121 Verb verb;
2122
reed@google.com51bbe752013-01-17 22:07:50 +00002123 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002124 char const * const gFillTypeStrs[] = {
2125 "Winding",
2126 "EvenOdd",
2127 "InverseWinding",
2128 "InverseEvenOdd",
2129 };
2130 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2131 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002132 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 switch (verb) {
2134 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002135 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 break;
2137 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002138 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
2140 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002141 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002143 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002144 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002145 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002147 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 break;
2149 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002150 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 default:
2153 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2154 verb = kDone_Verb; // stop the loop
2155 break;
2156 }
caryclark1049f122015-04-20 08:31:59 -07002157 if (!wStream && builder.size()) {
2158 SkDebugf("%s", builder.c_str());
2159 builder.reset();
2160 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 }
caryclark66a5d8b2014-06-24 08:30:15 -07002162 if (wStream) {
2163 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002164 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165}
2166
reed@android.come522ca52009-11-23 20:10:41 +00002167void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002168 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002169}
2170
2171void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002172 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002173}
2174
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002175
Cary Clark0461e9f2017-08-25 15:13:38 -04002176bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002177 if ((fFillType & ~3) != 0) {
2178 return false;
2179 }
reed@google.comabf15c12011-01-18 20:35:51 +00002180
djsollen@google.com077348c2012-10-22 20:23:32 +00002181#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002182 if (!fBoundsIsDirty) {
2183 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002184
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002185 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002186 if (SkToBool(fIsFinite) != isFinite) {
2187 return false;
2188 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002189
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002190 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002191 // if we're empty, fBounds may be empty but translated, so we can't
2192 // necessarily compare to bounds directly
2193 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2194 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002195 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2196 return false;
2197 }
reed@android.come522ca52009-11-23 20:10:41 +00002198 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002199 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002200 if (!fBounds.isEmpty()) {
2201 return false;
2202 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002203 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002204 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002205 if (!fBounds.contains(bounds)) {
2206 return false;
2207 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002208 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002209 }
reed@android.come522ca52009-11-23 20:10:41 +00002210 }
2211 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002212#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002213 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002214}
reed@android.come522ca52009-11-23 20:10:41 +00002215
reed@google.com04863fa2011-05-15 04:08:24 +00002216///////////////////////////////////////////////////////////////////////////////
2217
reed@google.com0b7b9822011-05-16 12:29:27 +00002218static int sign(SkScalar x) { return x < 0; }
2219#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002220
robertphillipsc506e302014-09-16 09:43:31 -07002221enum DirChange {
2222 kLeft_DirChange,
2223 kRight_DirChange,
2224 kStraight_DirChange,
2225 kBackwards_DirChange,
2226
2227 kInvalid_DirChange
2228};
2229
2230
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002231static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002232 // The error epsilon was empirically derived; worse case round rects
2233 // with a mid point outset by 2x float epsilon in tests had an error
2234 // of 12.
2235 const int epsilon = 16;
2236 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2237 return false;
2238 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002239 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002240 int aBits = SkFloatAs2sCompliment(compA);
2241 int bBits = SkFloatAs2sCompliment(compB);
2242 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002243}
2244
caryclarkb4216502015-03-02 13:02:34 -08002245static bool approximately_zero_when_compared_to(double x, double y) {
2246 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002247}
2248
caryclarkb4216502015-03-02 13:02:34 -08002249
reed@google.com04863fa2011-05-15 04:08:24 +00002250// only valid for a single contour
2251struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002252 Convexicator()
2253 : fPtCount(0)
2254 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002255 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002256 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002257 , fIsCurve(false)
2258 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002259 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002260 // warnings
djsollen2f124632016-04-29 13:53:05 -07002261 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002262 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002263 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002264 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002265 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002266
2267 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002268 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002269 }
2270
2271 SkPath::Convexity getConvexity() const { return fConvexity; }
2272
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002273 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002274 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275
reed@google.com04863fa2011-05-15 04:08:24 +00002276 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002277 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002278 return;
2279 }
2280
2281 if (0 == fPtCount) {
2282 fCurrPt = pt;
2283 ++fPtCount;
2284 } else {
2285 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002286 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002287 if (!SkScalarIsFinite(lengthSqd)) {
2288 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002289 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002290 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002291 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002292 fCurrPt = pt;
2293 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002294 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 } else {
2296 SkASSERT(fPtCount > 2);
2297 this->addVec(vec);
2298 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002299
reed@google.com85b6e392011-05-15 20:25:17 +00002300 int sx = sign(vec.fX);
2301 int sy = sign(vec.fY);
2302 fDx += (sx != fSx);
2303 fDy += (sy != fSy);
2304 fSx = sx;
2305 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002306
reed@google.com85b6e392011-05-15 20:25:17 +00002307 if (fDx > 3 || fDy > 3) {
2308 fConvexity = SkPath::kConcave_Convexity;
2309 }
reed@google.com04863fa2011-05-15 04:08:24 +00002310 }
2311 }
2312 }
2313
2314 void close() {
2315 if (fPtCount > 2) {
2316 this->addVec(fFirstVec);
2317 }
2318 }
2319
caryclarkb4216502015-03-02 13:02:34 -08002320 DirChange directionChange(const SkVector& curVec) {
2321 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2322
2323 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2324 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2325 largest = SkTMax(largest, -smallest);
2326
2327 if (!almost_equal(largest, largest + cross)) {
2328 int sign = SkScalarSignAsInt(cross);
2329 if (sign) {
2330 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2331 }
2332 }
2333
2334 if (cross) {
2335 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2336 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2337 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2338 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2339 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2340 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2341 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2342 if (sign) {
2343 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2344 }
2345 }
2346 }
2347
Cary Clarkdf429f32017-11-08 11:44:31 -05002348 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2349 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2350 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2351 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002352 fLastVec.dot(curVec) < 0.0f) {
2353 return kBackwards_DirChange;
2354 }
2355
2356 return kStraight_DirChange;
2357 }
2358
Cary Clarkc8323aa2017-08-25 08:04:43 -04002359 bool hasBackwards() const {
2360 return fBackwards;
2361 }
caryclarkb4216502015-03-02 13:02:34 -08002362
caryclarkd3d1a982014-12-08 04:57:38 -08002363 bool isFinite() const {
2364 return fIsFinite;
2365 }
2366
caryclark5ccef572015-03-02 10:07:56 -08002367 void setCurve(bool isCurve) {
2368 fIsCurve = isCurve;
2369 }
2370
reed@google.com04863fa2011-05-15 04:08:24 +00002371private:
2372 void addVec(const SkVector& vec) {
2373 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002374 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002375 switch (dir) {
2376 case kLeft_DirChange: // fall through
2377 case kRight_DirChange:
2378 if (kInvalid_DirChange == fExpectedDir) {
2379 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002380 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2381 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002382 } else if (dir != fExpectedDir) {
2383 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002384 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002385 }
2386 fLastVec = vec;
2387 break;
2388 case kStraight_DirChange:
2389 break;
2390 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002391 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002392 // If any of the subsequent dir is non-backward, it'll be concave.
2393 // Otherwise, it's still convex.
2394 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002395 }
robertphillipsc506e302014-09-16 09:43:31 -07002396 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002397 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002398 break;
2399 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002400 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002401 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 }
2403 }
2404
caryclarkb4216502015-03-02 13:02:34 -08002405 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002406 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002408 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2409 // value with the current vec is deemed to be of a significant value.
2410 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002411 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002412 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002413 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002414 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002415 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002416 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002417 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002418 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002419};
2420
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002421SkPath::Convexity SkPath::internalGetConvexity() const {
2422 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002423 SkPoint pts[4];
2424 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002425 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002426
2427 int contourCount = 0;
2428 int count;
2429 Convexicator state;
2430
caryclarkd3d1a982014-12-08 04:57:38 -08002431 if (!isFinite()) {
2432 return kUnknown_Convexity;
2433 }
Brian Osman205a1262017-09-18 13:13:48 +00002434 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002435 switch (verb) {
2436 case kMove_Verb:
2437 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002438 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002439 return kConcave_Convexity;
2440 }
2441 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002442 // fall through
2443 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002444 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002445 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002446 break;
caryclark5ccef572015-03-02 10:07:56 -08002447 case kQuad_Verb:
2448 // fall through
2449 case kConic_Verb:
2450 // fall through
2451 case kCubic_Verb:
2452 count = 2 + (kCubic_Verb == verb);
2453 // As an additional enhancement, this could set curve true only
2454 // if the curve is nonlinear
2455 state.setCurve(true);
2456 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002457 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002458 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002459 state.close();
2460 count = 0;
2461 break;
2462 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002463 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002464 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002465 return kConcave_Convexity;
2466 }
2467
2468 for (int i = 1; i <= count; i++) {
2469 state.addPt(pts[i]);
2470 }
2471 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002472 if (!state.isFinite()) {
2473 return kUnknown_Convexity;
2474 }
reed@google.com04863fa2011-05-15 04:08:24 +00002475 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002476 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002477 return kConcave_Convexity;
2478 }
2479 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002480 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002481 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002482 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2483 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2484 fConvexity = Convexity::kConcave_Convexity;
2485 } else {
2486 fFirstDirection = state.getFirstDirection();
2487 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002488 }
2489 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002490}
reed@google.com69a99432012-01-10 18:00:10 +00002491
2492///////////////////////////////////////////////////////////////////////////////
2493
2494class ContourIter {
2495public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002496 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002497
2498 bool done() const { return fDone; }
2499 // if !done() then these may be called
2500 int count() const { return fCurrPtCount; }
2501 const SkPoint* pts() const { return fCurrPt; }
2502 void next();
2503
2504private:
2505 int fCurrPtCount;
2506 const SkPoint* fCurrPt;
2507 const uint8_t* fCurrVerb;
2508 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002509 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002510 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002511 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002512};
2513
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002514ContourIter::ContourIter(const SkPathRef& pathRef) {
2515 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002516 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002517 fCurrPt = pathRef.points();
2518 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002519 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002520 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002521 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002522 this->next();
2523}
2524
2525void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002526 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002527 fDone = true;
2528 }
2529 if (fDone) {
2530 return;
2531 }
2532
2533 // skip pts of prev contour
2534 fCurrPt += fCurrPtCount;
2535
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002536 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002537 int ptCount = 1; // moveTo
2538 const uint8_t* verbs = fCurrVerb;
2539
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002540 for (--verbs; verbs > fStopVerbs; --verbs) {
2541 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002542 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002543 goto CONTOUR_END;
2544 case SkPath::kLine_Verb:
2545 ptCount += 1;
2546 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002547 case SkPath::kConic_Verb:
2548 fCurrConicWeight += 1;
2549 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002550 case SkPath::kQuad_Verb:
2551 ptCount += 2;
2552 break;
2553 case SkPath::kCubic_Verb:
2554 ptCount += 3;
2555 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002556 case SkPath::kClose_Verb:
2557 break;
2558 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002559 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002560 break;
2561 }
2562 }
2563CONTOUR_END:
2564 fCurrPtCount = ptCount;
2565 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002566 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002567}
2568
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002569// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002570static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002571 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2572 // We may get 0 when the above subtracts underflow. We expect this to be
2573 // very rare and lazily promote to double.
2574 if (0 == cross) {
2575 double p0x = SkScalarToDouble(p0.fX);
2576 double p0y = SkScalarToDouble(p0.fY);
2577
2578 double p1x = SkScalarToDouble(p1.fX);
2579 double p1y = SkScalarToDouble(p1.fY);
2580
2581 double p2x = SkScalarToDouble(p2.fX);
2582 double p2y = SkScalarToDouble(p2.fY);
2583
2584 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2585 (p1y - p0y) * (p2x - p0x));
2586
2587 }
2588 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002589}
2590
reed@google.comc1ea60a2012-01-31 15:15:36 +00002591// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002592static int find_max_y(const SkPoint pts[], int count) {
2593 SkASSERT(count > 0);
2594 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002595 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002596 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002597 SkScalar y = pts[i].fY;
2598 if (y > max) {
2599 max = y;
2600 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002601 }
2602 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002603 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002604}
2605
reed@google.comcabaf1d2012-01-11 21:03:05 +00002606static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2607 int i = index;
2608 for (;;) {
2609 i = (i + inc) % n;
2610 if (i == index) { // we wrapped around, so abort
2611 break;
2612 }
2613 if (pts[index] != pts[i]) { // found a different point, success!
2614 break;
2615 }
2616 }
2617 return i;
2618}
2619
reed@google.comc1ea60a2012-01-31 15:15:36 +00002620/**
2621 * Starting at index, and moving forward (incrementing), find the xmin and
2622 * xmax of the contiguous points that have the same Y.
2623 */
2624static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2625 int* maxIndexPtr) {
2626 const SkScalar y = pts[index].fY;
2627 SkScalar min = pts[index].fX;
2628 SkScalar max = min;
2629 int minIndex = index;
2630 int maxIndex = index;
2631 for (int i = index + 1; i < n; ++i) {
2632 if (pts[i].fY != y) {
2633 break;
2634 }
2635 SkScalar x = pts[i].fX;
2636 if (x < min) {
2637 min = x;
2638 minIndex = i;
2639 } else if (x > max) {
2640 max = x;
2641 maxIndex = i;
2642 }
2643 }
2644 *maxIndexPtr = maxIndex;
2645 return minIndex;
2646}
2647
reed026beb52015-06-10 14:23:15 -07002648static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2649 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002650}
2651
reed@google.comac8543f2012-01-30 20:51:25 +00002652/*
2653 * We loop through all contours, and keep the computed cross-product of the
2654 * contour that contained the global y-max. If we just look at the first
2655 * contour, we may find one that is wound the opposite way (correctly) since
2656 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2657 * that is outer most (or at least has the global y-max) before we can consider
2658 * its cross product.
2659 */
reed026beb52015-06-10 14:23:15 -07002660bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002661 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2662 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002663 return true;
2664 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002665
2666 // don't want to pay the cost for computing this if it
2667 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002668 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2669 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002670 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002671 return false;
2672 }
reed@google.com69a99432012-01-10 18:00:10 +00002673
reed026beb52015-06-10 14:23:15 -07002674 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002675
reed@google.comac8543f2012-01-30 20:51:25 +00002676 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002677 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002678 SkScalar ymaxCross = 0;
2679
reed@google.com69a99432012-01-10 18:00:10 +00002680 for (; !iter.done(); iter.next()) {
2681 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002682 if (n < 3) {
2683 continue;
2684 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002685
reed@google.comcabaf1d2012-01-11 21:03:05 +00002686 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002687 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002688 int index = find_max_y(pts, n);
2689 if (pts[index].fY < ymax) {
2690 continue;
2691 }
2692
2693 // If there is more than 1 distinct point at the y-max, we take the
2694 // x-min and x-max of them and just subtract to compute the dir.
2695 if (pts[(index + 1) % n].fY == pts[index].fY) {
2696 int maxIndex;
2697 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2698 if (minIndex == maxIndex) {
2699 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002700 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002701 SkASSERT(pts[minIndex].fY == pts[index].fY);
2702 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2703 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2704 // we just subtract the indices, and let that auto-convert to
2705 // SkScalar, since we just want - or + to signal the direction.
2706 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002707 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002708 TRY_CROSSPROD:
2709 // Find a next and prev index to use for the cross-product test,
2710 // but we try to find pts that form non-zero vectors from pts[index]
2711 //
2712 // Its possible that we can't find two non-degenerate vectors, so
2713 // we have to guard our search (e.g. all the pts could be in the
2714 // same place).
2715
2716 // we pass n - 1 instead of -1 so we don't foul up % operator by
2717 // passing it a negative LH argument.
2718 int prev = find_diff_pt(pts, index, n, n - 1);
2719 if (prev == index) {
2720 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002721 continue;
2722 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002723 int next = find_diff_pt(pts, index, n, 1);
2724 SkASSERT(next != index);
2725 cross = cross_prod(pts[prev], pts[index], pts[next]);
2726 // if we get a zero and the points are horizontal, then we look at the spread in
2727 // x-direction. We really should continue to walk away from the degeneracy until
2728 // there is a divergence.
2729 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2730 // construct the subtract so we get the correct Direction below
2731 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002732 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002733 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002734
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002735 if (cross) {
2736 // record our best guess so far
2737 ymax = pts[index].fY;
2738 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002739 }
2740 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002741 if (ymaxCross) {
2742 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002743 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002744 return true;
2745 } else {
2746 return false;
2747 }
reed@google.comac8543f2012-01-30 20:51:25 +00002748}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749
2750///////////////////////////////////////////////////////////////////////////////
2751
caryclark9aacd902015-12-14 08:38:09 -08002752static bool between(SkScalar a, SkScalar b, SkScalar c) {
2753 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2754 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2755 return (a - b) * (c - b) <= 0;
2756}
2757
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2759 SkScalar t) {
2760 SkScalar A = c3 + 3*(c1 - c2) - c0;
2761 SkScalar B = 3*(c2 - c1 - c1 + c0);
2762 SkScalar C = 3*(c1 - c0);
2763 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002764 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765}
2766
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002767template <size_t N> static void find_minmax(const SkPoint pts[],
2768 SkScalar* minPtr, SkScalar* maxPtr) {
2769 SkScalar min, max;
2770 min = max = pts[0].fX;
2771 for (size_t i = 1; i < N; ++i) {
2772 min = SkMinScalar(min, pts[i].fX);
2773 max = SkMaxScalar(max, pts[i].fX);
2774 }
2775 *minPtr = min;
2776 *maxPtr = max;
2777}
2778
caryclark9cb5d752015-12-18 04:35:24 -08002779static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2780 if (start.fY == end.fY) {
2781 return between(start.fX, x, end.fX) && x != end.fX;
2782 } else {
2783 return x == start.fX && y == start.fY;
2784 }
2785}
2786
caryclark9aacd902015-12-14 08:38:09 -08002787static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002788 SkScalar y0 = pts[0].fY;
2789 SkScalar y3 = pts[3].fY;
2790
2791 int dir = 1;
2792 if (y0 > y3) {
2793 SkTSwap(y0, y3);
2794 dir = -1;
2795 }
2796 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002797 return 0;
2798 }
caryclark9cb5d752015-12-18 04:35:24 -08002799 if (checkOnCurve(x, y, pts[0], pts[3])) {
2800 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002801 return 0;
2802 }
caryclark9cb5d752015-12-18 04:35:24 -08002803 if (y == y3) {
2804 return 0;
2805 }
2806
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 // quickreject or quickaccept
2808 SkScalar min, max;
2809 find_minmax<4>(pts, &min, &max);
2810 if (x < min) {
2811 return 0;
2812 }
2813 if (x > max) {
2814 return dir;
2815 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002818 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002819 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002820 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002821 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002822 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002823 if (SkScalarNearlyEqual(xt, x)) {
2824 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2825 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002826 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002827 }
2828 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 return xt < x ? dir : 0;
2830}
2831
caryclark9aacd902015-12-14 08:38:09 -08002832static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 SkPoint dst[10];
2834 int n = SkChopCubicAtYExtrema(pts, dst);
2835 int w = 0;
2836 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002837 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 }
2839 return w;
2840}
2841
caryclark9aacd902015-12-14 08:38:09 -08002842static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2843 SkASSERT(src);
2844 SkASSERT(t >= 0 && t <= 1);
2845 SkScalar src2w = src[2] * w;
2846 SkScalar C = src[0];
2847 SkScalar A = src[4] - 2 * src2w + C;
2848 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002849 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002850}
2851
2852
2853static double conic_eval_denominator(SkScalar w, SkScalar t) {
2854 SkScalar B = 2 * (w - 1);
2855 SkScalar C = 1;
2856 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002857 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002858}
2859
2860static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2861 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 SkScalar y0 = pts[0].fY;
2863 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002864
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865 int dir = 1;
2866 if (y0 > y2) {
2867 SkTSwap(y0, y2);
2868 dir = -1;
2869 }
caryclark9aacd902015-12-14 08:38:09 -08002870 if (y < y0 || y > y2) {
2871 return 0;
2872 }
caryclark9cb5d752015-12-18 04:35:24 -08002873 if (checkOnCurve(x, y, pts[0], pts[2])) {
2874 *onCurveCount += 1;
2875 return 0;
2876 }
caryclark9aacd902015-12-14 08:38:09 -08002877 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 return 0;
2879 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
caryclark9aacd902015-12-14 08:38:09 -08002881 SkScalar roots[2];
2882 SkScalar A = pts[2].fY;
2883 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2884 SkScalar C = pts[0].fY;
2885 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2886 B -= C; // B = b*w - w * yCept + yCept - a
2887 C -= y;
2888 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2889 SkASSERT(n <= 1);
2890 SkScalar xt;
2891 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002892 // zero roots are returned only when y0 == y
2893 // Need [0] if dir == 1
2894 // and [2] if dir == -1
2895 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002896 } else {
2897 SkScalar t = roots[0];
2898 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2899 }
2900 if (SkScalarNearlyEqual(xt, x)) {
2901 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2902 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002903 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002904 }
2905 }
2906 return xt < x ? dir : 0;
2907}
2908
2909static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2910 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2911 if (y0 == y1) {
2912 return true;
2913 }
2914 if (y0 < y1) {
2915 return y1 <= y2;
2916 } else {
2917 return y1 >= y2;
2918 }
2919}
2920
2921static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2922 int* onCurveCount) {
2923 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002924 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002925 // If the data points are very large, the conic may not be monotonic but may also
2926 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002927 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2928 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2929 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002930 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2931 }
2932 return w;
2933}
2934
2935static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2936 SkScalar y0 = pts[0].fY;
2937 SkScalar y2 = pts[2].fY;
2938
2939 int dir = 1;
2940 if (y0 > y2) {
2941 SkTSwap(y0, y2);
2942 dir = -1;
2943 }
2944 if (y < y0 || y > y2) {
2945 return 0;
2946 }
caryclark9cb5d752015-12-18 04:35:24 -08002947 if (checkOnCurve(x, y, pts[0], pts[2])) {
2948 *onCurveCount += 1;
2949 return 0;
2950 }
caryclark9aacd902015-12-14 08:38:09 -08002951 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002952 return 0;
2953 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002954 // bounds check on X (not required. is it faster?)
2955#if 0
2956 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2957 return 0;
2958 }
2959#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002960
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002961 SkScalar roots[2];
2962 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2963 2 * (pts[1].fY - pts[0].fY),
2964 pts[0].fY - y,
2965 roots);
2966 SkASSERT(n <= 1);
2967 SkScalar xt;
2968 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002969 // zero roots are returned only when y0 == y
2970 // Need [0] if dir == 1
2971 // and [2] if dir == -1
2972 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002973 } else {
2974 SkScalar t = roots[0];
2975 SkScalar C = pts[0].fX;
2976 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2977 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002978 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002979 }
caryclark9aacd902015-12-14 08:38:09 -08002980 if (SkScalarNearlyEqual(xt, x)) {
2981 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2982 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002983 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002984 }
2985 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 return xt < x ? dir : 0;
2987}
2988
caryclark9aacd902015-12-14 08:38:09 -08002989static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002990 SkPoint dst[5];
2991 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002992
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2994 n = SkChopQuadAtYExtrema(pts, dst);
2995 pts = dst;
2996 }
caryclark9aacd902015-12-14 08:38:09 -08002997 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002999 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 }
3001 return w;
3002}
3003
caryclark9aacd902015-12-14 08:38:09 -08003004static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 SkScalar x0 = pts[0].fX;
3006 SkScalar y0 = pts[0].fY;
3007 SkScalar x1 = pts[1].fX;
3008 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003009
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003011
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 int dir = 1;
3013 if (y0 > y1) {
3014 SkTSwap(y0, y1);
3015 dir = -1;
3016 }
caryclark9aacd902015-12-14 08:38:09 -08003017 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 return 0;
3019 }
caryclark9cb5d752015-12-18 04:35:24 -08003020 if (checkOnCurve(x, y, pts[0], pts[1])) {
3021 *onCurveCount += 1;
3022 return 0;
3023 }
3024 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003025 return 0;
3026 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003027 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003028
caryclark9aacd902015-12-14 08:38:09 -08003029 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003030 // zero cross means the point is on the line, and since the case where
3031 // y of the query point is at the end point is handled above, we can be
3032 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003033 if (x != x1 || y != pts[1].fY) {
3034 *onCurveCount += 1;
3035 }
caryclark9aacd902015-12-14 08:38:09 -08003036 dir = 0;
3037 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003038 dir = 0;
3039 }
3040 return dir;
3041}
3042
caryclark9aacd902015-12-14 08:38:09 -08003043static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3044 SkTDArray<SkVector>* tangents) {
3045 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3046 && !between(pts[2].fY, y, pts[3].fY)) {
3047 return;
3048 }
3049 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3050 && !between(pts[2].fX, x, pts[3].fX)) {
3051 return;
3052 }
3053 SkPoint dst[10];
3054 int n = SkChopCubicAtYExtrema(pts, dst);
3055 for (int i = 0; i <= n; ++i) {
3056 SkPoint* c = &dst[i * 3];
3057 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003058 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003059 continue;
mbarbella276e6332016-05-31 14:44:01 -07003060 }
caryclark9aacd902015-12-14 08:38:09 -08003061 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3062 if (!SkScalarNearlyEqual(x, xt)) {
3063 continue;
3064 }
3065 SkVector tangent;
3066 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3067 tangents->push(tangent);
3068 }
3069}
3070
3071static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3072 SkTDArray<SkVector>* tangents) {
3073 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3074 return;
3075 }
3076 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3077 return;
3078 }
3079 SkScalar roots[2];
3080 SkScalar A = pts[2].fY;
3081 SkScalar B = pts[1].fY * w - y * w + y;
3082 SkScalar C = pts[0].fY;
3083 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3084 B -= C; // B = b*w - w * yCept + yCept - a
3085 C -= y;
3086 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3087 for (int index = 0; index < n; ++index) {
3088 SkScalar t = roots[index];
3089 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3090 if (!SkScalarNearlyEqual(x, xt)) {
3091 continue;
3092 }
3093 SkConic conic(pts, w);
3094 tangents->push(conic.evalTangentAt(t));
3095 }
3096}
3097
3098static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3099 SkTDArray<SkVector>* tangents) {
3100 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3101 return;
3102 }
3103 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3104 return;
3105 }
3106 SkScalar roots[2];
3107 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3108 2 * (pts[1].fY - pts[0].fY),
3109 pts[0].fY - y,
3110 roots);
3111 for (int index = 0; index < n; ++index) {
3112 SkScalar t = roots[index];
3113 SkScalar C = pts[0].fX;
3114 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3115 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003116 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003117 if (!SkScalarNearlyEqual(x, xt)) {
3118 continue;
3119 }
3120 tangents->push(SkEvalQuadTangentAt(pts, t));
3121 }
3122}
3123
3124static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3125 SkTDArray<SkVector>* tangents) {
3126 SkScalar y0 = pts[0].fY;
3127 SkScalar y1 = pts[1].fY;
3128 if (!between(y0, y, y1)) {
3129 return;
3130 }
3131 SkScalar x0 = pts[0].fX;
3132 SkScalar x1 = pts[1].fX;
3133 if (!between(x0, x, x1)) {
3134 return;
3135 }
3136 SkScalar dx = x1 - x0;
3137 SkScalar dy = y1 - y0;
3138 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3139 return;
3140 }
3141 SkVector v;
3142 v.set(dx, dy);
3143 tangents->push(v);
3144}
3145
reed@google.com4db592c2013-10-30 17:39:43 +00003146static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3147 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3148}
3149
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150bool SkPath::contains(SkScalar x, SkScalar y) const {
3151 bool isInverse = this->isInverseFillType();
3152 if (this->isEmpty()) {
3153 return isInverse;
3154 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003155
reed@google.com4db592c2013-10-30 17:39:43 +00003156 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003157 return isInverse;
3158 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003159
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160 SkPath::Iter iter(*this, true);
3161 bool done = false;
3162 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003163 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003164 do {
3165 SkPoint pts[4];
3166 switch (iter.next(pts, false)) {
3167 case SkPath::kMove_Verb:
3168 case SkPath::kClose_Verb:
3169 break;
3170 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003171 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172 break;
3173 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003174 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003175 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003176 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003177 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003178 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003179 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003180 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003181 break;
3182 case SkPath::kDone_Verb:
3183 done = true;
3184 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003185 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003187 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3188 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3189 if (evenOddFill) {
3190 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003191 }
caryclark9aacd902015-12-14 08:38:09 -08003192 if (w) {
3193 return !isInverse;
3194 }
3195 if (onCurveCount <= 1) {
3196 return SkToBool(onCurveCount) ^ isInverse;
3197 }
3198 if ((onCurveCount & 1) || evenOddFill) {
3199 return SkToBool(onCurveCount & 1) ^ isInverse;
3200 }
halcanary9d524f22016-03-29 09:03:52 -07003201 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003202 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3203 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003204 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003205 SkTDArray<SkVector> tangents;
3206 do {
3207 SkPoint pts[4];
3208 int oldCount = tangents.count();
3209 switch (iter.next(pts, false)) {
3210 case SkPath::kMove_Verb:
3211 case SkPath::kClose_Verb:
3212 break;
3213 case SkPath::kLine_Verb:
3214 tangent_line(pts, x, y, &tangents);
3215 break;
3216 case SkPath::kQuad_Verb:
3217 tangent_quad(pts, x, y, &tangents);
3218 break;
3219 case SkPath::kConic_Verb:
3220 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3221 break;
3222 case SkPath::kCubic_Verb:
3223 tangent_cubic(pts, x, y, &tangents);
3224 break;
3225 case SkPath::kDone_Verb:
3226 done = true;
3227 break;
3228 }
3229 if (tangents.count() > oldCount) {
3230 int last = tangents.count() - 1;
3231 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003232 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003233 tangents.remove(last);
3234 } else {
3235 for (int index = 0; index < last; ++index) {
3236 const SkVector& test = tangents[index];
3237 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003238 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3239 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003240 tangents.remove(last);
3241 tangents.removeShuffle(index);
3242 break;
3243 }
3244 }
3245 }
3246 }
3247 } while (!done);
3248 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003249}
fmalitaaa0df4e2015-12-01 09:13:23 -08003250
3251int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3252 SkScalar w, SkPoint pts[], int pow2) {
3253 const SkConic conic(p0, p1, p2, w);
3254 return conic.chopIntoQuadsPOW2(pts, pow2);
3255}
bsalomonedc743a2016-06-01 09:42:31 -07003256
3257bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3258 unsigned* start) {
3259 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3260 return false;
3261 }
3262 SkPath::RawIter iter(path);
3263 SkPoint verbPts[4];
3264 SkPath::Verb v;
3265 SkPoint rectPts[5];
3266 int rectPtCnt = 0;
3267 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3268 switch (v) {
3269 case SkPath::kMove_Verb:
3270 if (0 != rectPtCnt) {
3271 return false;
3272 }
3273 rectPts[0] = verbPts[0];
3274 ++rectPtCnt;
3275 break;
3276 case SkPath::kLine_Verb:
3277 if (5 == rectPtCnt) {
3278 return false;
3279 }
3280 rectPts[rectPtCnt] = verbPts[1];
3281 ++rectPtCnt;
3282 break;
3283 case SkPath::kClose_Verb:
3284 if (4 == rectPtCnt) {
3285 rectPts[4] = rectPts[0];
3286 rectPtCnt = 5;
3287 }
3288 break;
3289 default:
3290 return false;
3291 }
3292 }
3293 if (rectPtCnt < 5) {
3294 return false;
3295 }
3296 if (rectPts[0] != rectPts[4]) {
3297 return false;
3298 }
bsalomon057ae8a2016-07-24 05:37:26 -07003299 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3300 // and pts 1 and 2 the opposite vertical or horizontal edge).
3301 bool vec03IsVertical;
3302 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3303 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3304 // Make sure it has non-zero width and height
3305 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003306 return false;
3307 }
bsalomon057ae8a2016-07-24 05:37:26 -07003308 vec03IsVertical = true;
3309 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3310 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3311 // Make sure it has non-zero width and height
3312 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3313 return false;
3314 }
3315 vec03IsVertical = false;
3316 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003317 return false;
3318 }
bsalomon057ae8a2016-07-24 05:37:26 -07003319 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3320 // set if it is on the bottom edge.
3321 unsigned sortFlags =
3322 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3323 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3324 switch (sortFlags) {
3325 case 0b00:
3326 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3327 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3328 *start = 0;
3329 break;
3330 case 0b01:
3331 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3332 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3333 *start = 1;
3334 break;
3335 case 0b10:
3336 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3337 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3338 *start = 3;
3339 break;
3340 case 0b11:
3341 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3342 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3343 *start = 2;
3344 break;
bsalomonedc743a2016-06-01 09:42:31 -07003345 }
bsalomonedc743a2016-06-01 09:42:31 -07003346 return true;
3347}
bsalomon21af9ca2016-08-25 12:29:23 -07003348
3349void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3350 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3351 SkASSERT(!oval.isEmpty());
3352 SkASSERT(sweepAngle);
3353
3354 path->reset();
3355 path->setIsVolatile(true);
3356 path->setFillType(SkPath::kWinding_FillType);
3357 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3358 path->addOval(oval);
3359 return;
3360 }
3361 if (useCenter) {
3362 path->moveTo(oval.centerX(), oval.centerY());
3363 }
3364 // Arc to mods at 360 and drawArc is not supposed to.
3365 bool forceMoveTo = !useCenter;
3366 while (sweepAngle <= -360.f) {
3367 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3368 startAngle -= 180.f;
3369 path->arcTo(oval, startAngle, -180.f, false);
3370 startAngle -= 180.f;
3371 forceMoveTo = false;
3372 sweepAngle += 360.f;
3373 }
3374 while (sweepAngle >= 360.f) {
3375 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3376 startAngle += 180.f;
3377 path->arcTo(oval, startAngle, 180.f, false);
3378 startAngle += 180.f;
3379 forceMoveTo = false;
3380 sweepAngle -= 360.f;
3381 }
3382 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3383 if (useCenter) {
3384 path->close();
3385 }
3386}
Mike Reed0d7dac82017-02-02 17:45:56 -08003387
3388///////////////////////////////////////////////////////////////////////////////////////////////////
3389#include "SkNx.h"
3390
3391static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3392 SkScalar ts[2];
3393 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3394 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3395 SkASSERT(n >= 0 && n <= 2);
3396 for (int i = 0; i < n; ++i) {
3397 extremas[i] = SkEvalQuadAt(src, ts[i]);
3398 }
3399 extremas[n] = src[2];
3400 return n + 1;
3401}
3402
3403static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3404 SkConic conic(src[0], src[1], src[2], w);
3405 SkScalar ts[2];
3406 int n = conic.findXExtrema(ts);
3407 n += conic.findYExtrema(&ts[n]);
3408 SkASSERT(n >= 0 && n <= 2);
3409 for (int i = 0; i < n; ++i) {
3410 extremas[i] = conic.evalAt(ts[i]);
3411 }
3412 extremas[n] = src[2];
3413 return n + 1;
3414}
3415
3416static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3417 SkScalar ts[4];
3418 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3419 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3420 SkASSERT(n >= 0 && n <= 4);
3421 for (int i = 0; i < n; ++i) {
3422 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3423 }
3424 extremas[n] = src[3];
3425 return n + 1;
3426}
3427
Mike Reed8d3196b2017-02-03 11:34:13 -05003428SkRect SkPath::computeTightBounds() const {
3429 if (0 == this->countVerbs()) {
3430 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003431 }
3432
Mike Reed8d3196b2017-02-03 11:34:13 -05003433 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3434 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003435 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003436
Mike Reed0d7dac82017-02-02 17:45:56 -08003437 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3438 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003439 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003440
3441 // initial with the first MoveTo, so we don't have to check inside the switch
3442 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003443 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003444 for (;;) {
3445 int count = 0;
3446 switch (iter.next(pts)) {
3447 case SkPath::kMove_Verb:
3448 extremas[0] = pts[0];
3449 count = 1;
3450 break;
3451 case SkPath::kLine_Verb:
3452 extremas[0] = pts[1];
3453 count = 1;
3454 break;
3455 case SkPath::kQuad_Verb:
3456 count = compute_quad_extremas(pts, extremas);
3457 break;
3458 case SkPath::kConic_Verb:
3459 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3460 break;
3461 case SkPath::kCubic_Verb:
3462 count = compute_cubic_extremas(pts, extremas);
3463 break;
3464 case SkPath::kClose_Verb:
3465 break;
3466 case SkPath::kDone_Verb:
3467 goto DONE;
3468 }
3469 for (int i = 0; i < count; ++i) {
3470 Sk2s tmp = from_point(extremas[i]);
3471 min = Sk2s::Min(min, tmp);
3472 max = Sk2s::Max(max, tmp);
3473 }
3474 }
3475DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003476 SkRect bounds;
3477 min.store((SkPoint*)&bounds.fLeft);
3478 max.store((SkPoint*)&bounds.fRight);
3479 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003480}
Cary Clarkdf429f32017-11-08 11:44:31 -05003481
3482bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3483 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3484}
3485
3486bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3487 const SkPoint& p3, bool exact) {
3488 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3489 SkPointPriv::EqualsWithinTolerance(p2, p3);
3490}
3491
3492bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3493 const SkPoint& p3, const SkPoint& p4, bool exact) {
3494 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3495 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3496 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3497 SkPointPriv::EqualsWithinTolerance(p3, p4);
3498}