blob: ff4208a346dbdbdceb8732025ac6802882a0e813 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
Cary Clark48c464a2018-04-16 12:06:07 -0400438 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
Cary Clark88ba9712018-04-12 14:00:24 -0400449
caryclark@google.comf68154a2012-11-21 15:18:06 +0000450bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400451 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400453 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400454 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400455 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
456 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarka7651562018-04-17 09:30:14 -0400457 const SkPoint* lastCountedPt = nullptr; // point creating 3rd corner
caryclark@google.com56f233a2012-11-19 13:06:06 +0000458 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400459 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400460 lineStart.set(0, 0);
Cary Clark48c464a2018-04-16 12:06:07 -0400461 signed char directions[] = {-1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000462 bool closedOrMoved = false;
Cary Clark8540e112018-04-11 14:30:27 -0400463 bool addedLine = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700465 bool insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400466 bool accumulatingRect = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000467 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000468 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700469 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
470 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000472 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700474 insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400475 accumulatingRect = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 case kLine_Verb: {
Cary Clark8540e112018-04-11 14:30:27 -0400477 if (accumulatingRect) {
Cary Clarka7651562018-04-17 09:30:14 -0400478 lastCountedPt = pts;
479 }
480 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400481 lastPt = pts;
482 }
Cary Clark88ba9712018-04-12 14:00:24 -0400483 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
484 SkVector lineDelta = lineEnd - lineStart;
485 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000486 return false; // diagonal
487 }
Cary Clark88ba9712018-04-12 14:00:24 -0400488 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 break; // single point on side OK
490 }
Cary Clarkd4228472018-04-13 07:07:04 -0400491 addedLine = true;
Cary Clark48c464a2018-04-16 12:06:07 -0400492 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400494 directions[0] = nextDirection;
Cary Clark88ba9712018-04-12 14:00:24 -0400495 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 corners = 1;
497 closedOrMoved = false;
498 break;
499 }
500 if (closedOrMoved) {
501 return false; // closed followed by a line
502 }
Cary Clark48c464a2018-04-16 12:06:07 -0400503 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000504 break; // colinear with first
505 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 closedOrMoved = autoClose;
Cary Clark88ba9712018-04-12 14:00:24 -0400507 lineStart = lineEnd;
Cary Clark48c464a2018-04-16 12:06:07 -0400508 if (directions[corners - 1] == nextDirection) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 break; // colinear segment
510 }
Cary Clark48c464a2018-04-16 12:06:07 -0400511 if (corners >= 4) {
512 return false; // too many direction changes
513 }
514 directions[corners++] = nextDirection;
515 // opposite lines must point in opposite directions; xoring them should equal 2
516 if (corners == 3) {
517 if ((directions[0] ^ directions[2]) != 2) {
518 return false;
519 }
Cary Clarka7651562018-04-17 09:30:14 -0400520 accumulatingRect = false;
Cary Clark48c464a2018-04-16 12:06:07 -0400521 } else if (corners == 4) {
522 if ((directions[1] ^ directions[3]) != 2) {
523 return false;
524 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 }
526 break;
527 }
528 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000529 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 case kCubic_Verb:
531 return false; // quadratic, cubic not allowed
532 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400533 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700534 insertClose = true;
535 *currVerb -= 1; // try move again afterwards
536 goto addMissingClose;
537 }
Cary Clark8540e112018-04-11 14:30:27 -0400538 if (!addedLine) {
539 firstPt = pts;
540 accumulatingRect = true;
541 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400542 closeXY = *firstPt - *lastPt;
543 if (closeXY.fX && closeXY.fY) {
544 return false; // we're diagonal, abort
545 }
Cary Clark8540e112018-04-11 14:30:27 -0400546 accumulatingRect = false;
547 }
Cary Clark88ba9712018-04-12 14:00:24 -0400548 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000549 closedOrMoved = true;
550 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000551 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000552 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000553 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000554 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000555 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700556addMissingClose:
557 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000558 }
559 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400560 if (corners < 3 || corners > 4) {
561 return false;
562 }
Cary Clark1cd60982018-04-17 11:53:34 -0400563 closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400564 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400565 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000566 if (!result) {
567 // check if we are just an incomplete rectangle, in which case we can
568 // return true, but not claim to be closed.
569 // e.g.
570 // 3 sided rectangle
571 // 4 sided but the last edge is not long enough to reach the start
572 //
Cary Clark8540e112018-04-11 14:30:27 -0400573 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000574 // make sure the close-segment doesn't double-back on itself
Cary Clark48c464a2018-04-16 12:06:07 -0400575 if (3 == corners || (closeDirection ^ directions[1]) == 2) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000576 result = true;
577 autoClose = false; // we are not closed
578 }
579 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000580 if (savePts) {
581 *ptsPtr = savePts;
582 }
Cary Clark5c715182018-04-09 16:07:11 -0400583 if (result && rect) {
Cary Clarka7651562018-04-17 09:30:14 -0400584 ptrdiff_t count = lastCountedPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400585 rect->set(firstPt, (int) count);
586 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000587 if (result && isClosed) {
588 *isClosed = autoClose;
589 }
590 if (result && direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400591 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000592 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000593 return result;
594}
595
robertphillips4f662e62014-12-29 14:06:51 -0800596bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000597 SkDEBUGCODE(this->validate();)
598 int currVerb = 0;
599 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400600 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000601}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000602
caryclark95bc5f32015-04-08 08:34:15 -0700603bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 SkDEBUGCODE(this->validate();)
605 int currVerb = 0;
606 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400608 SkRect testRects[2];
609 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return false;
611 }
Cary Clark5c715182018-04-09 16:07:11 -0400612 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 if (testRects[0].contains(testRects[1])) {
614 if (rects) {
615 rects[0] = testRects[0];
616 rects[1] = testRects[1];
617 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000618 if (dirs) {
619 dirs[0] = testDirs[0];
620 dirs[1] = testDirs[1];
621 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000622 return true;
623 }
624 if (testRects[1].contains(testRects[0])) {
625 if (rects) {
626 rects[0] = testRects[1];
627 rects[1] = testRects[0];
628 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000629 if (dirs) {
630 dirs[0] = testDirs[1];
631 dirs[1] = testDirs[0];
632 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000633 return true;
634 }
635 }
636 return false;
637}
638
Mike Reed0c3137c2018-02-20 13:57:05 -0500639bool SkPath::isOval(SkRect* bounds) const {
640 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
641}
642
643bool SkPath::isRRect(SkRRect* rrect) const {
644 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
645}
646
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647int SkPath::countPoints() const {
648 return fPathRef->countPoints();
649}
650
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 SkDEBUGCODE(this->validate();)
653
654 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000655 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800657 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659}
660
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000661SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
663 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000664 }
665 return SkPoint::Make(0, 0);
666}
667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668int SkPath::countVerbs() const {
669 return fPathRef->countVerbs();
670}
671
672static inline void copy_verbs_reverse(uint8_t* inorderDst,
673 const uint8_t* reversedSrc,
674 int count) {
675 for (int i = 0; i < count; ++i) {
676 inorderDst[i] = reversedSrc[~i];
677 }
678}
679
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000680int SkPath::getVerbs(uint8_t dst[], int max) const {
681 SkDEBUGCODE(this->validate();)
682
683 SkASSERT(max >= 0);
684 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 int count = SkMin32(max, fPathRef->countVerbs());
686 copy_verbs_reverse(dst, fPathRef->verbs(), count);
687 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000688}
689
reed@google.com294dd7b2011-10-11 11:58:32 +0000690bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 SkDEBUGCODE(this->validate();)
692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 if (count > 0) {
695 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000696 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000698 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000700 if (lastPt) {
701 lastPt->set(0, 0);
702 }
703 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704}
705
caryclarkaec25102015-04-29 08:28:30 -0700706void SkPath::setPt(int index, SkScalar x, SkScalar y) {
707 SkDEBUGCODE(this->validate();)
708
709 int count = fPathRef->countPoints();
710 if (count <= index) {
711 return;
712 } else {
713 SkPathRef::Editor ed(&fPathRef);
714 ed.atPoint(index)->set(x, y);
715 }
716}
717
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718void SkPath::setLastPt(SkScalar x, SkScalar y) {
719 SkDEBUGCODE(this->validate();)
720
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 if (count == 0) {
723 this->moveTo(x, y);
724 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 SkPathRef::Editor ed(&fPathRef);
726 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 }
728}
729
reed@google.com04863fa2011-05-15 04:08:24 +0000730void SkPath::setConvexity(Convexity c) {
731 if (fConvexity != c) {
732 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000733 }
734}
735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736//////////////////////////////////////////////////////////////////////////////
737// Construction methods
738
reed026beb52015-06-10 14:23:15 -0700739#define DIRTY_AFTER_EDIT \
740 do { \
741 fConvexity = kUnknown_Convexity; \
742 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000743 } while (0)
744
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745void SkPath::incReserve(U16CPU inc) {
746 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748 SkDEBUGCODE(this->validate();)
749}
750
751void SkPath::moveTo(SkScalar x, SkScalar y) {
752 SkDEBUGCODE(this->validate();)
753
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000754 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000756 // remember our index
757 fLastMoveToIndex = fPathRef->countPoints();
758
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000759 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700760
761 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762}
763
764void SkPath::rMoveTo(SkScalar x, SkScalar y) {
765 SkPoint pt;
766 this->getLastPt(&pt);
767 this->moveTo(pt.fX + x, pt.fY + y);
768}
769
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000770void SkPath::injectMoveToIfNeeded() {
771 if (fLastMoveToIndex < 0) {
772 SkScalar x, y;
773 if (fPathRef->countVerbs() == 0) {
774 x = y = 0;
775 } else {
776 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
777 x = pt.fX;
778 y = pt.fY;
779 }
780 this->moveTo(x, y);
781 }
782}
783
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784void SkPath::lineTo(SkScalar x, SkScalar y) {
785 SkDEBUGCODE(this->validate();)
786
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000787 this->injectMoveToIfNeeded();
788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000789 SkPathRef::Editor ed(&fPathRef);
790 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791
reed@google.comb54455e2011-05-16 14:16:04 +0000792 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793}
794
795void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000796 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkPoint pt;
798 this->getLastPt(&pt);
799 this->lineTo(pt.fX + x, pt.fY + y);
800}
801
802void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
803 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000804
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000805 this->injectMoveToIfNeeded();
806
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000807 SkPathRef::Editor ed(&fPathRef);
808 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 pts[0].set(x1, y1);
810 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000811
reed@google.comb54455e2011-05-16 14:16:04 +0000812 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813}
814
815void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000816 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 SkPoint pt;
818 this->getLastPt(&pt);
819 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
820}
821
reed@google.com277c3f82013-05-31 15:17:50 +0000822void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
823 SkScalar w) {
824 // check for <= 0 or NaN with this test
825 if (!(w > 0)) {
826 this->lineTo(x2, y2);
827 } else if (!SkScalarIsFinite(w)) {
828 this->lineTo(x1, y1);
829 this->lineTo(x2, y2);
830 } else if (SK_Scalar1 == w) {
831 this->quadTo(x1, y1, x2, y2);
832 } else {
833 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000834
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000835 this->injectMoveToIfNeeded();
836
reed@google.com277c3f82013-05-31 15:17:50 +0000837 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000838 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000839 pts[0].set(x1, y1);
840 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000841
reed@google.com277c3f82013-05-31 15:17:50 +0000842 DIRTY_AFTER_EDIT;
843 }
844}
845
846void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
847 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000848 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000849 SkPoint pt;
850 this->getLastPt(&pt);
851 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
852}
853
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
855 SkScalar x3, SkScalar y3) {
856 SkDEBUGCODE(this->validate();)
857
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000858 this->injectMoveToIfNeeded();
859
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000860 SkPathRef::Editor ed(&fPathRef);
861 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 pts[0].set(x1, y1);
863 pts[1].set(x2, y2);
864 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865
reed@google.comb54455e2011-05-16 14:16:04 +0000866 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867}
868
869void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
870 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000871 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 SkPoint pt;
873 this->getLastPt(&pt);
874 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
875 pt.fX + x3, pt.fY + y3);
876}
877
878void SkPath::close() {
879 SkDEBUGCODE(this->validate();)
880
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 case kLine_Verb:
885 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000886 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 case kMove_Verb: {
889 SkPathRef::Editor ed(&fPathRef);
890 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000892 }
reed@google.com277c3f82013-05-31 15:17:50 +0000893 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000894 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000895 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000896 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000897 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000898 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000899 }
900 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000901
902 // signal that we need a moveTo to follow us (unless we're done)
903#if 0
904 if (fLastMoveToIndex >= 0) {
905 fLastMoveToIndex = ~fLastMoveToIndex;
906 }
907#else
908 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
909#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910}
911
912///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000913
fmalitac08d53e2015-11-17 09:53:29 -0800914namespace {
915
916template <unsigned N>
917class PointIterator {
918public:
919 PointIterator(SkPath::Direction dir, unsigned startIndex)
920 : fCurrent(startIndex % N)
921 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
922
923 const SkPoint& current() const {
924 SkASSERT(fCurrent < N);
925 return fPts[fCurrent];
926 }
927
928 const SkPoint& next() {
929 fCurrent = (fCurrent + fAdvance) % N;
930 return this->current();
931 }
932
933protected:
934 SkPoint fPts[N];
935
936private:
937 unsigned fCurrent;
938 unsigned fAdvance;
939};
940
941class RectPointIterator : public PointIterator<4> {
942public:
943 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
944 : PointIterator(dir, startIndex) {
945
946 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
947 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
948 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
949 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
950 }
951};
952
953class OvalPointIterator : public PointIterator<4> {
954public:
955 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
956 : PointIterator(dir, startIndex) {
957
958 const SkScalar cx = oval.centerX();
959 const SkScalar cy = oval.centerY();
960
961 fPts[0] = SkPoint::Make(cx, oval.fTop);
962 fPts[1] = SkPoint::Make(oval.fRight, cy);
963 fPts[2] = SkPoint::Make(cx, oval.fBottom);
964 fPts[3] = SkPoint::Make(oval.fLeft, cy);
965 }
966};
967
968class RRectPointIterator : public PointIterator<8> {
969public:
970 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
971 : PointIterator(dir, startIndex) {
972
973 const SkRect& bounds = rrect.getBounds();
974 const SkScalar L = bounds.fLeft;
975 const SkScalar T = bounds.fTop;
976 const SkScalar R = bounds.fRight;
977 const SkScalar B = bounds.fBottom;
978
979 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
980 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
981 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
982 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
983 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
984 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
985 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
986 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
987 }
988};
989
990} // anonymous namespace
991
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000992static void assert_known_direction(int dir) {
993 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
994}
995
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800997 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998}
999
1000void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
1001 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001002 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
1003}
1004
1005void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001006 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001007 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001008 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001009 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001010 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001011
fmalitac08d53e2015-11-17 09:53:29 -08001012 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013
fmalitac08d53e2015-11-17 09:53:29 -08001014 const int kVerbs = 5; // moveTo + 3x lineTo + close
1015 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016
fmalitac08d53e2015-11-17 09:53:29 -08001017 RectPointIterator iter(rect, dir, startIndex);
1018
1019 this->moveTo(iter.current());
1020 this->lineTo(iter.next());
1021 this->lineTo(iter.next());
1022 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001024
1025 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001026}
1027
reed@google.com744faba2012-05-29 19:54:52 +00001028void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1029 SkDEBUGCODE(this->validate();)
1030 if (count <= 0) {
1031 return;
1032 }
1033
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001034 fLastMoveToIndex = fPathRef->countPoints();
1035
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001036 // +close makes room for the extra kClose_Verb
1037 SkPathRef::Editor ed(&fPathRef, count+close, count);
1038
1039 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001040 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001041 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1042 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001043 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001044
reed@google.com744faba2012-05-29 19:54:52 +00001045 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001046 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001047 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001048 }
1049
reed@google.com744faba2012-05-29 19:54:52 +00001050 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001051 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001052}
1053
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001054#include "SkGeometry.h"
1055
reedf90ea012015-01-29 12:03:58 -08001056static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1057 SkPoint* pt) {
1058 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001059 // Chrome uses this path to move into and out of ovals. If not
1060 // treated as a special case the moves can distort the oval's
1061 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001062 pt->set(oval.fRight, oval.centerY());
1063 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064 } else if (0 == oval.width() && 0 == oval.height()) {
1065 // Chrome will sometimes create 0 radius round rects. Having degenerate
1066 // quad segments in the path prevents the path from being recognized as
1067 // a rect.
1068 // TODO: optimizing the case where only one of width or height is zero
1069 // should also be considered. This case, however, doesn't seem to be
1070 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001071 pt->set(oval.fRight, oval.fTop);
1072 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073 }
reedf90ea012015-01-29 12:03:58 -08001074 return false;
1075}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001076
reedd5d27d92015-02-09 13:54:43 -08001077// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1078//
1079static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1080 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1081 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1082 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001083
1084 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001085 loss in radians-conversion and/or sin/cos, we may end up with coincident
1086 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1087 of drawing a nearly complete circle (good).
1088 e.g. canvas.drawArc(0, 359.99, ...)
1089 -vs- canvas.drawArc(0, 359.9, ...)
1090 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091 */
reedd5d27d92015-02-09 13:54:43 -08001092 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001093 SkScalar sw = SkScalarAbs(sweepAngle);
1094 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1095 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1096 // make a guess at a tiny angle (in radians) to tweak by
1097 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1098 // not sure how much will be enough, so we use a loop
1099 do {
1100 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001101 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1102 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103 }
1104 }
reedd5d27d92015-02-09 13:54:43 -08001105 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1106}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001107
reed9e779d42015-02-17 11:43:14 -08001108/**
1109 * If this returns 0, then the caller should just line-to the singlePt, else it should
1110 * ignore singlePt and append the specified number of conics.
1111 */
reedd5d27d92015-02-09 13:54:43 -08001112static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001113 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1114 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001115 SkMatrix matrix;
1116
1117 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1118 matrix.postTranslate(oval.centerX(), oval.centerY());
1119
reed9e779d42015-02-17 11:43:14 -08001120 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1121 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001122 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001123 }
1124 return count;
reedd5d27d92015-02-09 13:54:43 -08001125}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001126
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001127void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129 SkRRect rrect;
1130 rrect.setRectRadii(rect, (const SkVector*) radii);
1131 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001132}
1133
reed@google.com4ed0fb72012-12-12 20:48:18 +00001134void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001135 // legacy start indices: 6 (CW) and 7(CCW)
1136 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1137}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001138
fmalitac08d53e2015-11-17 09:53:29 -08001139void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1140 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001141
caryclarkda707bf2015-11-19 14:47:43 -08001142 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001143 const SkRect& bounds = rrect.getBounds();
1144
Brian Salomon0a241ce2017-12-15 11:31:05 -05001145 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001146 // degenerate(rect) => radii points are collapsing
1147 this->addRect(bounds, dir, (startIndex + 1) / 2);
1148 } else if (rrect.isOval()) {
1149 // degenerate(oval) => line points are collapsing
1150 this->addOval(bounds, dir, startIndex / 2);
1151 } else {
1152 fFirstDirection = this->hasOnlyMoveTos() ?
1153 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1154
1155 SkAutoPathBoundsUpdate apbu(this, bounds);
1156 SkAutoDisableDirectionCheck addc(this);
1157
1158 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1159 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1160 const SkScalar weight = SK_ScalarRoot2Over2;
1161
1162 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1163 const int kVerbs = startsWithConic
1164 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1165 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1166 this->incReserve(kVerbs);
1167
1168 RRectPointIterator rrectIter(rrect, dir, startIndex);
1169 // Corner iterator indices follow the collapsed radii model,
1170 // adjusted such that the start pt is "behind" the radii start pt.
1171 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1172 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1173
1174 this->moveTo(rrectIter.current());
1175 if (startsWithConic) {
1176 for (unsigned i = 0; i < 3; ++i) {
1177 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1178 this->lineTo(rrectIter.next());
1179 }
1180 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1181 // final lineTo handled by close().
1182 } else {
1183 for (unsigned i = 0; i < 4; ++i) {
1184 this->lineTo(rrectIter.next());
1185 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1186 }
1187 }
1188 this->close();
1189
caryclarkda707bf2015-11-19 14:47:43 -08001190 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001191 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001192
fmalitac08d53e2015-11-17 09:53:29 -08001193 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1194 }
1195
1196 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001197}
1198
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001199bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001200 int count = fPathRef->countVerbs();
1201 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1202 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001203 if (*verbs == kLine_Verb ||
1204 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001205 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001206 *verbs == kCubic_Verb) {
1207 return false;
1208 }
1209 ++verbs;
1210 }
1211 return true;
1212}
1213
Brian Osmana2318572017-07-10 15:09:26 -04001214bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1215 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001216 if (count < 2) {
1217 return true;
1218 }
Brian Osmana2318572017-07-10 15:09:26 -04001219 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001220 const SkPoint& first = *pts;
1221 for (int index = 1; index < count; ++index) {
1222 if (first != pts[index]) {
1223 return false;
1224 }
1225 }
1226 return true;
1227}
1228
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001229void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1230 Direction dir) {
1231 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001232
humper@google.com75e3ca12013-04-08 21:44:11 +00001233 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001234 return;
1235 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001236
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001237 SkRRect rrect;
1238 rrect.setRectXY(rect, rx, ry);
1239 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001240}
1241
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001243 // legacy start index: 1
1244 this->addOval(oval, dir, 1);
1245}
1246
1247void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001248 assert_known_direction(dir);
1249
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001250 /* If addOval() is called after previous moveTo(),
1251 this path is still marked as an oval. This is used to
1252 fit into WebKit's calling sequences.
1253 We can't simply check isEmpty() in this case, as additional
1254 moveTo() would mark the path non empty.
1255 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001256 bool isOval = hasOnlyMoveTos();
1257 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001258 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 } else {
reed026beb52015-06-10 14:23:15 -07001260 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001261 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001262
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001263 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 SkAutoPathBoundsUpdate apbu(this, oval);
1265
fmalitac08d53e2015-11-17 09:53:29 -08001266 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1267 const int kVerbs = 6; // moveTo + 4x conicTo + close
1268 this->incReserve(kVerbs);
1269
1270 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1271 // The corner iterator pts are tracking "behind" the oval/radii pts.
1272 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001273 const SkScalar weight = SK_ScalarRoot2Over2;
1274
fmalitac08d53e2015-11-17 09:53:29 -08001275 this->moveTo(ovalIter.current());
1276 for (unsigned i = 0; i < 4; ++i) {
1277 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001278 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280
fmalitac08d53e2015-11-17 09:53:29 -08001281 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1282
robertphillips@google.com466310d2013-12-03 16:43:54 +00001283 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001284
bsalomon78d58d12016-05-27 09:17:04 -07001285 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001286}
1287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1289 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001290 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 }
1292}
1293
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1295 bool forceMoveTo) {
1296 if (oval.width() < 0 || oval.height() < 0) {
1297 return;
1298 }
1299
reedf90ea012015-01-29 12:03:58 -08001300 if (fPathRef->countVerbs() == 0) {
1301 forceMoveTo = true;
1302 }
1303
1304 SkPoint lonePt;
1305 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1306 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1307 return;
1308 }
1309
reedd5d27d92015-02-09 13:54:43 -08001310 SkVector startV, stopV;
1311 SkRotationDirection dir;
1312 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1313
reed9e779d42015-02-17 11:43:14 -08001314 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001315
1316 // At this point, we know that the arc is not a lone point, but startV == stopV
1317 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1318 // cannot handle it.
1319 if (startV == stopV) {
1320 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1321 SkScalar radiusX = oval.width() / 2;
1322 SkScalar radiusY = oval.height() / 2;
1323 // We cannot use SkScalarSinCos function in the next line because
1324 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1325 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001326 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001327 // make sin(endAngle) to be 0 which will then draw a dot.
1328 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1329 oval.centerY() + radiusY * sk_float_sin(endAngle));
1330 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1331 return;
1332 }
1333
reedd5d27d92015-02-09 13:54:43 -08001334 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001335 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001336 if (count) {
1337 this->incReserve(count * 2 + 1);
1338 const SkPoint& pt = conics[0].fPts[0];
1339 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1340 for (int i = 0; i < count; ++i) {
1341 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1342 }
reed9e779d42015-02-17 11:43:14 -08001343 } else {
1344 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001345 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346}
1347
caryclark55d49052016-01-23 05:07:04 -08001348// This converts the SVG arc to conics.
1349// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1350// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1351// See also SVG implementation notes:
1352// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1353// Note that arcSweep bool value is flipped from the original implementation.
1354void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1355 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001356 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001357 SkPoint srcPts[2];
1358 this->getLastPt(&srcPts[0]);
1359 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1360 // joining the endpoints.
1361 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1362 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001363 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001364 return;
1365 }
1366 // If the current point and target point for the arc are identical, it should be treated as a
1367 // zero length path. This ensures continuity in animations.
1368 srcPts[1].set(x, y);
1369 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001370 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001371 return;
1372 }
1373 rx = SkScalarAbs(rx);
1374 ry = SkScalarAbs(ry);
1375 SkVector midPointDistance = srcPts[0] - srcPts[1];
1376 midPointDistance *= 0.5f;
1377
1378 SkMatrix pointTransform;
1379 pointTransform.setRotate(-angle);
1380
1381 SkPoint transformedMidPoint;
1382 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1383 SkScalar squareRx = rx * rx;
1384 SkScalar squareRy = ry * ry;
1385 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1386 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1387
1388 // Check if the radii are big enough to draw the arc, scale radii if not.
1389 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1390 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1391 if (radiiScale > 1) {
1392 radiiScale = SkScalarSqrt(radiiScale);
1393 rx *= radiiScale;
1394 ry *= radiiScale;
1395 }
1396
1397 pointTransform.setScale(1 / rx, 1 / ry);
1398 pointTransform.preRotate(-angle);
1399
1400 SkPoint unitPts[2];
1401 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1402 SkVector delta = unitPts[1] - unitPts[0];
1403
1404 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1405 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1406
1407 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1408 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1409 scaleFactor = -scaleFactor;
1410 }
1411 delta.scale(scaleFactor);
1412 SkPoint centerPoint = unitPts[0] + unitPts[1];
1413 centerPoint *= 0.5f;
1414 centerPoint.offset(-delta.fY, delta.fX);
1415 unitPts[0] -= centerPoint;
1416 unitPts[1] -= centerPoint;
1417 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1418 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1419 SkScalar thetaArc = theta2 - theta1;
1420 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1421 thetaArc += SK_ScalarPI * 2;
1422 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1423 thetaArc -= SK_ScalarPI * 2;
1424 }
1425 pointTransform.setRotate(angle);
1426 pointTransform.preScale(rx, ry);
1427
Cary Clark1acd3c72017-12-08 11:37:01 -05001428#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001429 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001430#else
1431 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1432 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1433#endif
caryclark55d49052016-01-23 05:07:04 -08001434 SkScalar thetaWidth = thetaArc / segments;
1435 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1436 if (!SkScalarIsFinite(t)) {
1437 return;
1438 }
1439 SkScalar startTheta = theta1;
1440 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001441#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1442 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1443 return scalar == SkScalarFloorToScalar(scalar);
1444 };
1445 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1446 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1447 scalar_is_integer(x) && scalar_is_integer(y);
1448#endif
caryclark55d49052016-01-23 05:07:04 -08001449 for (int i = 0; i < segments; ++i) {
1450 SkScalar endTheta = startTheta + thetaWidth;
1451 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1452
1453 unitPts[1].set(cosEndTheta, sinEndTheta);
1454 unitPts[1] += centerPoint;
1455 unitPts[0] = unitPts[1];
1456 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1457 SkPoint mapped[2];
1458 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001459 /*
1460 Computing the arc width introduces rounding errors that cause arcs to start
1461 outside their marks. A round rect may lose convexity as a result. If the input
1462 values are on integers, place the conic on integers as well.
1463 */
1464#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1465 if (expectIntegers) {
1466 SkScalar* mappedScalars = &mapped[0].fX;
1467 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1468 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1469 }
1470 }
1471#endif
caryclark55d49052016-01-23 05:07:04 -08001472 this->conicTo(mapped[0], mapped[1], w);
1473 startTheta = endTheta;
1474 }
1475}
1476
1477void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1478 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1479 SkPoint currentPoint;
1480 this->getLastPt(&currentPoint);
1481 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1482}
1483
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001484void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 if (oval.isEmpty() || 0 == sweepAngle) {
1486 return;
1487 }
1488
1489 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1490
1491 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001492 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1493 // See SkPath::addOval() docs.
1494 SkScalar startOver90 = startAngle / 90.f;
1495 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1496 SkScalar error = startOver90 - startOver90I;
1497 if (SkScalarNearlyEqual(error, 0)) {
1498 // Index 1 is at startAngle == 0.
1499 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1500 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1501 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1502 (unsigned) startIndex);
1503 return;
1504 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 }
bsalomon1978ce22016-05-31 10:42:16 -07001506 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507}
1508
1509/*
1510 Need to handle the case when the angle is sharp, and our computed end-points
1511 for the arc go behind pt1 and/or p2...
1512*/
reedc7789042015-01-29 12:59:11 -08001513void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001514 if (radius == 0) {
1515 this->lineTo(x1, y1);
1516 return;
1517 }
1518
1519 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001520
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 // need to know our prev pt so we can construct tangent vectors
1522 {
1523 SkPoint start;
1524 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001525 // Handle degenerate cases by adding a line to the first point and
1526 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 before.setNormalize(x1 - start.fX, y1 - start.fY);
1528 after.setNormalize(x2 - x1, y2 - y1);
1529 }
reed@google.comabf15c12011-01-18 20:35:51 +00001530
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 SkScalar cosh = SkPoint::DotProduct(before, after);
1532 SkScalar sinh = SkPoint::CrossProduct(before, after);
1533
1534 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001535 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 return;
1537 }
reed@google.comabf15c12011-01-18 20:35:51 +00001538
Mike Reeda99b6ce2017-02-04 11:04:26 -05001539 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540
Mike Reeda99b6ce2017-02-04 11:04:26 -05001541 SkScalar xx = x1 - dist * before.fX;
1542 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001543 after.setLength(dist);
1544 this->lineTo(xx, yy);
1545 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1546 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547}
1548
1549///////////////////////////////////////////////////////////////////////////////
1550
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001551void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 SkMatrix matrix;
1553
1554 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001555 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556}
1557
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001558void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001559 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001561 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 SkPoint pts[4];
1563 Verb verb;
1564
Cary Clark9480d822017-10-19 18:01:13 -04001565 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001566 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 while ((verb = iter.next(pts)) != kDone_Verb) {
1568 switch (verb) {
1569 case kMove_Verb:
1570 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001571 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1572 injectMoveToIfNeeded(); // In case last contour is closed
1573 this->lineTo(pts[0]);
1574 } else {
1575 this->moveTo(pts[0]);
1576 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 break;
1578 case kLine_Verb:
1579 proc(matrix, &pts[1], &pts[1], 1);
1580 this->lineTo(pts[1]);
1581 break;
1582 case kQuad_Verb:
1583 proc(matrix, &pts[1], &pts[1], 2);
1584 this->quadTo(pts[1], pts[2]);
1585 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001586 case kConic_Verb:
1587 proc(matrix, &pts[1], &pts[1], 2);
1588 this->conicTo(pts[1], pts[2], iter.conicWeight());
1589 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 case kCubic_Verb:
1591 proc(matrix, &pts[1], &pts[1], 3);
1592 this->cubicTo(pts[1], pts[2], pts[3]);
1593 break;
1594 case kClose_Verb:
1595 this->close();
1596 break;
1597 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001598 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001600 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
1602}
1603
1604///////////////////////////////////////////////////////////////////////////////
1605
reed@google.com277c3f82013-05-31 15:17:50 +00001606static int pts_in_verb(unsigned verb) {
1607 static const uint8_t gPtsInVerb[] = {
1608 1, // kMove
1609 1, // kLine
1610 2, // kQuad
1611 2, // kConic
1612 3, // kCubic
1613 0, // kClose
1614 0 // kDone
1615 };
1616
1617 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1618 return gPtsInVerb[verb];
1619}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621// ignore the last point of the 1st contour
1622void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001623 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1624 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 return;
1626 }
caryclark51c56782016-11-07 05:09:28 -08001627 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1628 SkASSERT(verbsEnd[0] == kMove_Verb);
1629 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1630 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631
caryclark51c56782016-11-07 05:09:28 -08001632 while (verbs < verbsEnd) {
1633 uint8_t v = *verbs++;
1634 pts -= pts_in_verb(v);
1635 switch (v) {
1636 case kMove_Verb:
1637 // if the path has multiple contours, stop after reversing the last
1638 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001640 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 break;
1642 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001643 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001645 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001646 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001647 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001649 this->cubicTo(pts[2], pts[1], pts[0]);
1650 break;
1651 case kClose_Verb:
1652 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 break;
1654 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001655 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656 break;
1657 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 }
1659}
1660
reed@google.com63d73742012-01-10 15:33:12 +00001661void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001664 const SkPoint* pts = src.fPathRef->pointsEnd();
1665 // we will iterator through src's verbs backwards
1666 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1667 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001668 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001669
1670 bool needMove = true;
1671 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 while (verbs < verbsEnd) {
1673 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001674 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001675
1676 if (needMove) {
1677 --pts;
1678 this->moveTo(pts->fX, pts->fY);
1679 needMove = false;
1680 }
1681 pts -= n;
1682 switch (v) {
1683 case kMove_Verb:
1684 if (needClose) {
1685 this->close();
1686 needClose = false;
1687 }
1688 needMove = true;
1689 pts += 1; // so we see the point in "if (needMove)" above
1690 break;
1691 case kLine_Verb:
1692 this->lineTo(pts[0]);
1693 break;
1694 case kQuad_Verb:
1695 this->quadTo(pts[1], pts[0]);
1696 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001697 case kConic_Verb:
1698 this->conicTo(pts[1], pts[0], *--conicWeights);
1699 break;
reed@google.com63d73742012-01-10 15:33:12 +00001700 case kCubic_Verb:
1701 this->cubicTo(pts[2], pts[1], pts[0]);
1702 break;
1703 case kClose_Verb:
1704 needClose = true;
1705 break;
1706 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001707 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001708 }
1709 }
1710}
1711
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712///////////////////////////////////////////////////////////////////////////////
1713
1714void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1715 SkMatrix matrix;
1716
1717 matrix.setTranslate(dx, dy);
1718 this->transform(matrix, dst);
1719}
1720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1722 int level = 2) {
1723 if (--level >= 0) {
1724 SkPoint tmp[7];
1725
1726 SkChopCubicAtHalf(pts, tmp);
1727 subdivide_cubic_to(path, &tmp[0], level);
1728 subdivide_cubic_to(path, &tmp[3], level);
1729 } else {
1730 path->cubicTo(pts[1], pts[2], pts[3]);
1731 }
1732}
1733
1734void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1735 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001736 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 dst = (SkPath*)this;
1738 }
1739
tomhudson@google.com8d430182011-06-06 19:11:19 +00001740 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 SkPath tmp;
1742 tmp.fFillType = fFillType;
1743
1744 SkPath::Iter iter(*this, false);
1745 SkPoint pts[4];
1746 SkPath::Verb verb;
1747
reed@google.com4a3b7142012-05-16 17:16:46 +00001748 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 switch (verb) {
1750 case kMove_Verb:
1751 tmp.moveTo(pts[0]);
1752 break;
1753 case kLine_Verb:
1754 tmp.lineTo(pts[1]);
1755 break;
1756 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001757 // promote the quad to a conic
1758 tmp.conicTo(pts[1], pts[2],
1759 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001761 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001762 tmp.conicTo(pts[1], pts[2],
1763 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001764 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 case kCubic_Verb:
1766 subdivide_cubic_to(&tmp, pts);
1767 break;
1768 case kClose_Verb:
1769 tmp.close();
1770 break;
1771 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001772 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 break;
1774 }
1775 }
1776
1777 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 SkPathRef::Editor ed(&dst->fPathRef);
1779 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001780 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1783
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001786 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001787 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001789
reed026beb52015-06-10 14:23:15 -07001790 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1791 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 } else {
1793 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001794 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1795 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001796 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001797 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1798 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001799 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001800 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001801 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001802 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001803 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001804 }
1805 }
1806
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 SkDEBUGCODE(dst->validate();)
1808 }
1809}
1810
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811///////////////////////////////////////////////////////////////////////////////
1812///////////////////////////////////////////////////////////////////////////////
1813
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001815 kEmptyContour_SegmentState, // The current contour is empty. We may be
1816 // starting processing or we may have just
1817 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001818 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1819 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1820 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821};
1822
1823SkPath::Iter::Iter() {
1824#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001825 fPts = nullptr;
1826 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001829 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830#endif
1831 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001832 fVerbs = nullptr;
1833 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 fNeedClose = false;
1835}
1836
1837SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1838 this->setPath(path, forceClose);
1839}
1840
1841void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 fPts = path.fPathRef->points();
1843 fVerbs = path.fPathRef->verbs();
1844 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001845 fConicWeights = path.fPathRef->conicWeights();
1846 if (fConicWeights) {
1847 fConicWeights -= 1; // begin one behind
1848 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001850 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 fForceClose = SkToU8(forceClose);
1852 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001853 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854}
1855
1856bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001857 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 return false;
1859 }
1860 if (fForceClose) {
1861 return true;
1862 }
1863
1864 const uint8_t* verbs = fVerbs;
1865 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001866
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 if (kMove_Verb == *(verbs - 1)) {
1868 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 }
1870
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001871 while (verbs > stop) {
1872 // verbs points one beyond the current verb, decrement first.
1873 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 if (kMove_Verb == v) {
1875 break;
1876 }
1877 if (kClose_Verb == v) {
1878 return true;
1879 }
1880 }
1881 return false;
1882}
1883
1884SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001885 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001887 // A special case: if both points are NaN, SkPoint::operation== returns
1888 // false, but the iterator expects that they are treated as the same.
1889 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001890 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1891 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001892 return kClose_Verb;
1893 }
1894
reed@google.com9e25dbf2012-05-15 17:05:38 +00001895 pts[0] = fLastPt;
1896 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 fLastPt = fMoveTo;
1898 fCloseLine = true;
1899 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001900 } else {
1901 pts[0] = fMoveTo;
1902 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904}
1905
reed@google.com9e25dbf2012-05-15 17:05:38 +00001906const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 if (fSegmentState == kAfterMove_SegmentState) {
1908 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1913 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001914 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916}
1917
caryclarke8c56662015-07-14 11:19:26 -07001918void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 // We need to step over anything that will not move the current draw point
1920 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001921 const uint8_t* lastMoveVerb = nullptr;
1922 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001923 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 SkPoint lastPt = fLastPt;
1925 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001926 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 switch (verb) {
1928 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 // Keep a record of this most recent move
1930 lastMoveVerb = fVerbs;
1931 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001932 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001933 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001934 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 fPts++;
1936 break;
1937
1938 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001939 // A close when we are in a segment is always valid except when it
1940 // follows a move which follows a segment.
1941 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 return;
1943 }
1944 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001945 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 break;
1947
1948 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001949 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 if (lastMoveVerb) {
1951 fVerbs = lastMoveVerb;
1952 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001953 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 return;
1955 }
1956 return;
1957 }
1958 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001959 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 fPts++;
1961 break;
1962
reed@google.com277c3f82013-05-31 15:17:50 +00001963 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001965 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 if (lastMoveVerb) {
1967 fVerbs = lastMoveVerb;
1968 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001969 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 return;
1971 }
1972 return;
1973 }
1974 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001977 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 break;
1979
1980 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001981 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 if (lastMoveVerb) {
1983 fVerbs = lastMoveVerb;
1984 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001985 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 return;
1987 }
1988 return;
1989 }
1990 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001991 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 fPts += 3;
1993 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001994
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001996 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 }
1998 }
1999}
2000
reed@google.com4a3b7142012-05-16 17:16:46 +00002001SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002002 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002005 // Close the curve if requested and if there is some curve to close
2006 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002007 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 return kLine_Verb;
2009 }
2010 fNeedClose = false;
2011 return kClose_Verb;
2012 }
2013 return kDone_Verb;
2014 }
2015
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002016 // fVerbs is one beyond the current verb, decrement first
2017 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002018 const SkPoint* SK_RESTRICT srcPts = fPts;
2019 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020
2021 switch (verb) {
2022 case kMove_Verb:
2023 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002024 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025 verb = this->autoClose(pts);
2026 if (verb == kClose_Verb) {
2027 fNeedClose = false;
2028 }
2029 return (Verb)verb;
2030 }
2031 if (fVerbs == fVerbStop) { // might be a trailing moveto
2032 return kDone_Verb;
2033 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002034 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002035 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002037 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002038 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 fNeedClose = fForceClose;
2040 break;
2041 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002042 pts[0] = this->cons_moveTo();
2043 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 fLastPt = srcPts[0];
2045 fCloseLine = false;
2046 srcPts += 1;
2047 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002048 case kConic_Verb:
2049 fConicWeights += 1;
2050 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002052 pts[0] = this->cons_moveTo();
2053 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 fLastPt = srcPts[1];
2055 srcPts += 2;
2056 break;
2057 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002058 pts[0] = this->cons_moveTo();
2059 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 fLastPt = srcPts[2];
2061 srcPts += 3;
2062 break;
2063 case kClose_Verb:
2064 verb = this->autoClose(pts);
2065 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002066 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 } else {
2068 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002069 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002071 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 break;
2073 }
2074 fPts = srcPts;
2075 return (Verb)verb;
2076}
2077
2078///////////////////////////////////////////////////////////////////////////////
2079
Ben Wagner4d1955c2017-03-10 13:08:15 -05002080#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002081#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002082#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002083
reed@google.com51bbe752013-01-17 22:07:50 +00002084static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002085 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002086 str->append(label);
2087 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002088
reed@google.com51bbe752013-01-17 22:07:50 +00002089 const SkScalar* values = &pts[0].fX;
2090 count *= 2;
2091
2092 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002093 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002094 if (i < count - 1) {
2095 str->append(", ");
2096 }
2097 }
Mike Reed405b9d22018-01-09 15:11:08 -05002098 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002099 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002100 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002101 }
caryclark08fa28c2014-10-23 13:08:56 -07002102 str->append(");");
reede05fed02014-12-15 07:59:53 -08002103 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002104 str->append(" // ");
2105 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002106 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002107 if (i < count - 1) {
2108 str->append(", ");
2109 }
2110 }
2111 if (conicWeight >= 0) {
2112 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002113 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002114 }
2115 }
2116 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002117}
2118
caryclarke9562592014-09-15 09:26:09 -07002119void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002120 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 Iter iter(*this, forceClose);
2122 SkPoint pts[4];
2123 Verb verb;
2124
reed@google.com51bbe752013-01-17 22:07:50 +00002125 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002126 char const * const gFillTypeStrs[] = {
2127 "Winding",
2128 "EvenOdd",
2129 "InverseWinding",
2130 "InverseEvenOdd",
2131 };
2132 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2133 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002134 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 switch (verb) {
2136 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
2142 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002145 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002147 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002149 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002152 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 default:
2155 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2156 verb = kDone_Verb; // stop the loop
2157 break;
2158 }
caryclark1049f122015-04-20 08:31:59 -07002159 if (!wStream && builder.size()) {
2160 SkDebugf("%s", builder.c_str());
2161 builder.reset();
2162 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 }
caryclark66a5d8b2014-06-24 08:30:15 -07002164 if (wStream) {
2165 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002166 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002167}
2168
reed@android.come522ca52009-11-23 20:10:41 +00002169void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002170 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002171}
2172
2173void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002174 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002175}
2176
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002177
Cary Clark0461e9f2017-08-25 15:13:38 -04002178bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002179 if ((fFillType & ~3) != 0) {
2180 return false;
2181 }
reed@google.comabf15c12011-01-18 20:35:51 +00002182
djsollen@google.com077348c2012-10-22 20:23:32 +00002183#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002184 if (!fBoundsIsDirty) {
2185 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002186
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002187 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002188 if (SkToBool(fIsFinite) != isFinite) {
2189 return false;
2190 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002191
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002192 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002193 // if we're empty, fBounds may be empty but translated, so we can't
2194 // necessarily compare to bounds directly
2195 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2196 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002197 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2198 return false;
2199 }
reed@android.come522ca52009-11-23 20:10:41 +00002200 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002201 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002202 if (!fBounds.isEmpty()) {
2203 return false;
2204 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002205 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002206 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002207 if (!fBounds.contains(bounds)) {
2208 return false;
2209 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002210 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002211 }
reed@android.come522ca52009-11-23 20:10:41 +00002212 }
2213 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002214#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002215 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002216}
reed@android.come522ca52009-11-23 20:10:41 +00002217
reed@google.com04863fa2011-05-15 04:08:24 +00002218///////////////////////////////////////////////////////////////////////////////
2219
reed@google.com0b7b9822011-05-16 12:29:27 +00002220static int sign(SkScalar x) { return x < 0; }
2221#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002222
robertphillipsc506e302014-09-16 09:43:31 -07002223enum DirChange {
2224 kLeft_DirChange,
2225 kRight_DirChange,
2226 kStraight_DirChange,
2227 kBackwards_DirChange,
2228
2229 kInvalid_DirChange
2230};
2231
2232
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002233static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002234 // The error epsilon was empirically derived; worse case round rects
2235 // with a mid point outset by 2x float epsilon in tests had an error
2236 // of 12.
2237 const int epsilon = 16;
2238 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2239 return false;
2240 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002241 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002242 int aBits = SkFloatAs2sCompliment(compA);
2243 int bBits = SkFloatAs2sCompliment(compB);
2244 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002245}
2246
caryclarkb4216502015-03-02 13:02:34 -08002247static bool approximately_zero_when_compared_to(double x, double y) {
2248 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002249}
2250
caryclarkb4216502015-03-02 13:02:34 -08002251
reed@google.com04863fa2011-05-15 04:08:24 +00002252// only valid for a single contour
2253struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002254 Convexicator()
2255 : fPtCount(0)
2256 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002257 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002258 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002259 , fIsCurve(false)
2260 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002261 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002262 // warnings
djsollen2f124632016-04-29 13:53:05 -07002263 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002264 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002265 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002266 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002267 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002268
2269 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002270 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002271 }
2272
2273 SkPath::Convexity getConvexity() const { return fConvexity; }
2274
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002276 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002277
reed@google.com04863fa2011-05-15 04:08:24 +00002278 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002279 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002280 return;
2281 }
2282
2283 if (0 == fPtCount) {
2284 fCurrPt = pt;
2285 ++fPtCount;
2286 } else {
2287 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002288 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002289 if (!SkScalarIsFinite(lengthSqd)) {
2290 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002291 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002292 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002293 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 fCurrPt = pt;
2295 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002296 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002297 } else {
2298 SkASSERT(fPtCount > 2);
2299 this->addVec(vec);
2300 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002301
reed@google.com85b6e392011-05-15 20:25:17 +00002302 int sx = sign(vec.fX);
2303 int sy = sign(vec.fY);
2304 fDx += (sx != fSx);
2305 fDy += (sy != fSy);
2306 fSx = sx;
2307 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002308
reed@google.com85b6e392011-05-15 20:25:17 +00002309 if (fDx > 3 || fDy > 3) {
2310 fConvexity = SkPath::kConcave_Convexity;
2311 }
reed@google.com04863fa2011-05-15 04:08:24 +00002312 }
2313 }
2314 }
2315
2316 void close() {
2317 if (fPtCount > 2) {
2318 this->addVec(fFirstVec);
2319 }
2320 }
2321
caryclarkb4216502015-03-02 13:02:34 -08002322 DirChange directionChange(const SkVector& curVec) {
2323 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2324
2325 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2326 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2327 largest = SkTMax(largest, -smallest);
2328
2329 if (!almost_equal(largest, largest + cross)) {
2330 int sign = SkScalarSignAsInt(cross);
2331 if (sign) {
2332 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2333 }
2334 }
2335
2336 if (cross) {
2337 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2338 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2339 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2340 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2341 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2342 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2343 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2344 if (sign) {
2345 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2346 }
2347 }
2348 }
2349
Cary Clarkdf429f32017-11-08 11:44:31 -05002350 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2351 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2352 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2353 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002354 fLastVec.dot(curVec) < 0.0f) {
2355 return kBackwards_DirChange;
2356 }
2357
2358 return kStraight_DirChange;
2359 }
2360
Cary Clarkc8323aa2017-08-25 08:04:43 -04002361 bool hasBackwards() const {
2362 return fBackwards;
2363 }
caryclarkb4216502015-03-02 13:02:34 -08002364
caryclarkd3d1a982014-12-08 04:57:38 -08002365 bool isFinite() const {
2366 return fIsFinite;
2367 }
2368
caryclark5ccef572015-03-02 10:07:56 -08002369 void setCurve(bool isCurve) {
2370 fIsCurve = isCurve;
2371 }
2372
reed@google.com04863fa2011-05-15 04:08:24 +00002373private:
2374 void addVec(const SkVector& vec) {
2375 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002376 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002377 switch (dir) {
2378 case kLeft_DirChange: // fall through
2379 case kRight_DirChange:
2380 if (kInvalid_DirChange == fExpectedDir) {
2381 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002382 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2383 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002384 } else if (dir != fExpectedDir) {
2385 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002386 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002387 }
2388 fLastVec = vec;
2389 break;
2390 case kStraight_DirChange:
2391 break;
2392 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002393 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002394 // If any of the subsequent dir is non-backward, it'll be concave.
2395 // Otherwise, it's still convex.
2396 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002397 }
robertphillipsc506e302014-09-16 09:43:31 -07002398 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002399 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002400 break;
2401 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002402 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002403 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 }
2405 }
2406
caryclarkb4216502015-03-02 13:02:34 -08002407 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002408 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002410 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2411 // value with the current vec is deemed to be of a significant value.
2412 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002413 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002414 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002415 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002416 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002417 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002418 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002419 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002420 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002421};
2422
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002423SkPath::Convexity SkPath::internalGetConvexity() const {
2424 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002425 SkPoint pts[4];
2426 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002427 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002428
2429 int contourCount = 0;
2430 int count;
2431 Convexicator state;
2432
caryclarkd3d1a982014-12-08 04:57:38 -08002433 if (!isFinite()) {
2434 return kUnknown_Convexity;
2435 }
Brian Osman205a1262017-09-18 13:13:48 +00002436 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002437 switch (verb) {
2438 case kMove_Verb:
2439 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002440 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 return kConcave_Convexity;
2442 }
2443 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002444 // fall through
2445 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002446 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002447 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002448 break;
caryclark5ccef572015-03-02 10:07:56 -08002449 case kQuad_Verb:
2450 // fall through
2451 case kConic_Verb:
2452 // fall through
2453 case kCubic_Verb:
2454 count = 2 + (kCubic_Verb == verb);
2455 // As an additional enhancement, this could set curve true only
2456 // if the curve is nonlinear
2457 state.setCurve(true);
2458 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002459 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002460 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002461 state.close();
2462 count = 0;
2463 break;
2464 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002465 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002466 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002467 return kConcave_Convexity;
2468 }
2469
2470 for (int i = 1; i <= count; i++) {
2471 state.addPt(pts[i]);
2472 }
2473 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002474 if (!state.isFinite()) {
2475 return kUnknown_Convexity;
2476 }
reed@google.com04863fa2011-05-15 04:08:24 +00002477 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002478 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002479 return kConcave_Convexity;
2480 }
2481 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002482 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002483 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002484 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2485 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2486 fConvexity = Convexity::kConcave_Convexity;
2487 } else {
2488 fFirstDirection = state.getFirstDirection();
2489 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002490 }
2491 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002492}
reed@google.com69a99432012-01-10 18:00:10 +00002493
2494///////////////////////////////////////////////////////////////////////////////
2495
2496class ContourIter {
2497public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002498 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002499
2500 bool done() const { return fDone; }
2501 // if !done() then these may be called
2502 int count() const { return fCurrPtCount; }
2503 const SkPoint* pts() const { return fCurrPt; }
2504 void next();
2505
2506private:
2507 int fCurrPtCount;
2508 const SkPoint* fCurrPt;
2509 const uint8_t* fCurrVerb;
2510 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002511 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002512 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002513 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002514};
2515
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002516ContourIter::ContourIter(const SkPathRef& pathRef) {
2517 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002518 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002519 fCurrPt = pathRef.points();
2520 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002521 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002522 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002523 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002524 this->next();
2525}
2526
2527void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002528 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002529 fDone = true;
2530 }
2531 if (fDone) {
2532 return;
2533 }
2534
2535 // skip pts of prev contour
2536 fCurrPt += fCurrPtCount;
2537
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002538 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002539 int ptCount = 1; // moveTo
2540 const uint8_t* verbs = fCurrVerb;
2541
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002542 for (--verbs; verbs > fStopVerbs; --verbs) {
2543 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002544 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002545 goto CONTOUR_END;
2546 case SkPath::kLine_Verb:
2547 ptCount += 1;
2548 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002549 case SkPath::kConic_Verb:
2550 fCurrConicWeight += 1;
2551 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002552 case SkPath::kQuad_Verb:
2553 ptCount += 2;
2554 break;
2555 case SkPath::kCubic_Verb:
2556 ptCount += 3;
2557 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002558 case SkPath::kClose_Verb:
2559 break;
2560 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002561 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002562 break;
2563 }
2564 }
2565CONTOUR_END:
2566 fCurrPtCount = ptCount;
2567 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002568 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002569}
2570
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002571// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002572static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002573 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2574 // We may get 0 when the above subtracts underflow. We expect this to be
2575 // very rare and lazily promote to double.
2576 if (0 == cross) {
2577 double p0x = SkScalarToDouble(p0.fX);
2578 double p0y = SkScalarToDouble(p0.fY);
2579
2580 double p1x = SkScalarToDouble(p1.fX);
2581 double p1y = SkScalarToDouble(p1.fY);
2582
2583 double p2x = SkScalarToDouble(p2.fX);
2584 double p2y = SkScalarToDouble(p2.fY);
2585
2586 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2587 (p1y - p0y) * (p2x - p0x));
2588
2589 }
2590 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002591}
2592
reed@google.comc1ea60a2012-01-31 15:15:36 +00002593// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002594static int find_max_y(const SkPoint pts[], int count) {
2595 SkASSERT(count > 0);
2596 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002597 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002598 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002599 SkScalar y = pts[i].fY;
2600 if (y > max) {
2601 max = y;
2602 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002603 }
2604 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002605 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002606}
2607
reed@google.comcabaf1d2012-01-11 21:03:05 +00002608static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2609 int i = index;
2610 for (;;) {
2611 i = (i + inc) % n;
2612 if (i == index) { // we wrapped around, so abort
2613 break;
2614 }
2615 if (pts[index] != pts[i]) { // found a different point, success!
2616 break;
2617 }
2618 }
2619 return i;
2620}
2621
reed@google.comc1ea60a2012-01-31 15:15:36 +00002622/**
2623 * Starting at index, and moving forward (incrementing), find the xmin and
2624 * xmax of the contiguous points that have the same Y.
2625 */
2626static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2627 int* maxIndexPtr) {
2628 const SkScalar y = pts[index].fY;
2629 SkScalar min = pts[index].fX;
2630 SkScalar max = min;
2631 int minIndex = index;
2632 int maxIndex = index;
2633 for (int i = index + 1; i < n; ++i) {
2634 if (pts[i].fY != y) {
2635 break;
2636 }
2637 SkScalar x = pts[i].fX;
2638 if (x < min) {
2639 min = x;
2640 minIndex = i;
2641 } else if (x > max) {
2642 max = x;
2643 maxIndex = i;
2644 }
2645 }
2646 *maxIndexPtr = maxIndex;
2647 return minIndex;
2648}
2649
reed026beb52015-06-10 14:23:15 -07002650static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2651 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002652}
2653
reed@google.comac8543f2012-01-30 20:51:25 +00002654/*
2655 * We loop through all contours, and keep the computed cross-product of the
2656 * contour that contained the global y-max. If we just look at the first
2657 * contour, we may find one that is wound the opposite way (correctly) since
2658 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2659 * that is outer most (or at least has the global y-max) before we can consider
2660 * its cross product.
2661 */
reed026beb52015-06-10 14:23:15 -07002662bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002663 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2664 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002665 return true;
2666 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002667
2668 // don't want to pay the cost for computing this if it
2669 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002670 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2671 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002672 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002673 return false;
2674 }
reed@google.com69a99432012-01-10 18:00:10 +00002675
reed026beb52015-06-10 14:23:15 -07002676 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002677
reed@google.comac8543f2012-01-30 20:51:25 +00002678 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002679 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002680 SkScalar ymaxCross = 0;
2681
reed@google.com69a99432012-01-10 18:00:10 +00002682 for (; !iter.done(); iter.next()) {
2683 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002684 if (n < 3) {
2685 continue;
2686 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002687
reed@google.comcabaf1d2012-01-11 21:03:05 +00002688 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002689 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002690 int index = find_max_y(pts, n);
2691 if (pts[index].fY < ymax) {
2692 continue;
2693 }
2694
2695 // If there is more than 1 distinct point at the y-max, we take the
2696 // x-min and x-max of them and just subtract to compute the dir.
2697 if (pts[(index + 1) % n].fY == pts[index].fY) {
2698 int maxIndex;
2699 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2700 if (minIndex == maxIndex) {
2701 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002702 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002703 SkASSERT(pts[minIndex].fY == pts[index].fY);
2704 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2705 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2706 // we just subtract the indices, and let that auto-convert to
2707 // SkScalar, since we just want - or + to signal the direction.
2708 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002709 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002710 TRY_CROSSPROD:
2711 // Find a next and prev index to use for the cross-product test,
2712 // but we try to find pts that form non-zero vectors from pts[index]
2713 //
2714 // Its possible that we can't find two non-degenerate vectors, so
2715 // we have to guard our search (e.g. all the pts could be in the
2716 // same place).
2717
2718 // we pass n - 1 instead of -1 so we don't foul up % operator by
2719 // passing it a negative LH argument.
2720 int prev = find_diff_pt(pts, index, n, n - 1);
2721 if (prev == index) {
2722 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002723 continue;
2724 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002725 int next = find_diff_pt(pts, index, n, 1);
2726 SkASSERT(next != index);
2727 cross = cross_prod(pts[prev], pts[index], pts[next]);
2728 // if we get a zero and the points are horizontal, then we look at the spread in
2729 // x-direction. We really should continue to walk away from the degeneracy until
2730 // there is a divergence.
2731 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2732 // construct the subtract so we get the correct Direction below
2733 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002734 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002735 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002736
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002737 if (cross) {
2738 // record our best guess so far
2739 ymax = pts[index].fY;
2740 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002741 }
2742 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002743 if (ymaxCross) {
2744 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002745 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002746 return true;
2747 } else {
2748 return false;
2749 }
reed@google.comac8543f2012-01-30 20:51:25 +00002750}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751
2752///////////////////////////////////////////////////////////////////////////////
2753
caryclark9aacd902015-12-14 08:38:09 -08002754static bool between(SkScalar a, SkScalar b, SkScalar c) {
2755 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2756 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2757 return (a - b) * (c - b) <= 0;
2758}
2759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2761 SkScalar t) {
2762 SkScalar A = c3 + 3*(c1 - c2) - c0;
2763 SkScalar B = 3*(c2 - c1 - c1 + c0);
2764 SkScalar C = 3*(c1 - c0);
2765 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002766 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002767}
2768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769template <size_t N> static void find_minmax(const SkPoint pts[],
2770 SkScalar* minPtr, SkScalar* maxPtr) {
2771 SkScalar min, max;
2772 min = max = pts[0].fX;
2773 for (size_t i = 1; i < N; ++i) {
2774 min = SkMinScalar(min, pts[i].fX);
2775 max = SkMaxScalar(max, pts[i].fX);
2776 }
2777 *minPtr = min;
2778 *maxPtr = max;
2779}
2780
caryclark9cb5d752015-12-18 04:35:24 -08002781static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2782 if (start.fY == end.fY) {
2783 return between(start.fX, x, end.fX) && x != end.fX;
2784 } else {
2785 return x == start.fX && y == start.fY;
2786 }
2787}
2788
caryclark9aacd902015-12-14 08:38:09 -08002789static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002790 SkScalar y0 = pts[0].fY;
2791 SkScalar y3 = pts[3].fY;
2792
2793 int dir = 1;
2794 if (y0 > y3) {
2795 SkTSwap(y0, y3);
2796 dir = -1;
2797 }
2798 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 return 0;
2800 }
caryclark9cb5d752015-12-18 04:35:24 -08002801 if (checkOnCurve(x, y, pts[0], pts[3])) {
2802 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002803 return 0;
2804 }
caryclark9cb5d752015-12-18 04:35:24 -08002805 if (y == y3) {
2806 return 0;
2807 }
2808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 // quickreject or quickaccept
2810 SkScalar min, max;
2811 find_minmax<4>(pts, &min, &max);
2812 if (x < min) {
2813 return 0;
2814 }
2815 if (x > max) {
2816 return dir;
2817 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002818
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002819 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002820 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002821 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002822 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002823 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002824 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002825 if (SkScalarNearlyEqual(xt, x)) {
2826 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2827 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002828 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002829 }
2830 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 return xt < x ? dir : 0;
2832}
2833
caryclark9aacd902015-12-14 08:38:09 -08002834static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 SkPoint dst[10];
2836 int n = SkChopCubicAtYExtrema(pts, dst);
2837 int w = 0;
2838 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002839 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 }
2841 return w;
2842}
2843
caryclark9aacd902015-12-14 08:38:09 -08002844static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2845 SkASSERT(src);
2846 SkASSERT(t >= 0 && t <= 1);
2847 SkScalar src2w = src[2] * w;
2848 SkScalar C = src[0];
2849 SkScalar A = src[4] - 2 * src2w + C;
2850 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002851 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002852}
2853
2854
2855static double conic_eval_denominator(SkScalar w, SkScalar t) {
2856 SkScalar B = 2 * (w - 1);
2857 SkScalar C = 1;
2858 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002859 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002860}
2861
2862static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2863 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 SkScalar y0 = pts[0].fY;
2865 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 int dir = 1;
2868 if (y0 > y2) {
2869 SkTSwap(y0, y2);
2870 dir = -1;
2871 }
caryclark9aacd902015-12-14 08:38:09 -08002872 if (y < y0 || y > y2) {
2873 return 0;
2874 }
caryclark9cb5d752015-12-18 04:35:24 -08002875 if (checkOnCurve(x, y, pts[0], pts[2])) {
2876 *onCurveCount += 1;
2877 return 0;
2878 }
caryclark9aacd902015-12-14 08:38:09 -08002879 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 return 0;
2881 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002882
caryclark9aacd902015-12-14 08:38:09 -08002883 SkScalar roots[2];
2884 SkScalar A = pts[2].fY;
2885 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2886 SkScalar C = pts[0].fY;
2887 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2888 B -= C; // B = b*w - w * yCept + yCept - a
2889 C -= y;
2890 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2891 SkASSERT(n <= 1);
2892 SkScalar xt;
2893 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002894 // zero roots are returned only when y0 == y
2895 // Need [0] if dir == 1
2896 // and [2] if dir == -1
2897 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002898 } else {
2899 SkScalar t = roots[0];
2900 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2901 }
2902 if (SkScalarNearlyEqual(xt, x)) {
2903 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2904 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002905 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002906 }
2907 }
2908 return xt < x ? dir : 0;
2909}
2910
2911static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2912 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2913 if (y0 == y1) {
2914 return true;
2915 }
2916 if (y0 < y1) {
2917 return y1 <= y2;
2918 } else {
2919 return y1 >= y2;
2920 }
2921}
2922
2923static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2924 int* onCurveCount) {
2925 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002926 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002927 // If the data points are very large, the conic may not be monotonic but may also
2928 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002929 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2930 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2931 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002932 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2933 }
2934 return w;
2935}
2936
2937static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2938 SkScalar y0 = pts[0].fY;
2939 SkScalar y2 = pts[2].fY;
2940
2941 int dir = 1;
2942 if (y0 > y2) {
2943 SkTSwap(y0, y2);
2944 dir = -1;
2945 }
2946 if (y < y0 || y > y2) {
2947 return 0;
2948 }
caryclark9cb5d752015-12-18 04:35:24 -08002949 if (checkOnCurve(x, y, pts[0], pts[2])) {
2950 *onCurveCount += 1;
2951 return 0;
2952 }
caryclark9aacd902015-12-14 08:38:09 -08002953 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002954 return 0;
2955 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002956 // bounds check on X (not required. is it faster?)
2957#if 0
2958 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2959 return 0;
2960 }
2961#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002962
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 SkScalar roots[2];
2964 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2965 2 * (pts[1].fY - pts[0].fY),
2966 pts[0].fY - y,
2967 roots);
2968 SkASSERT(n <= 1);
2969 SkScalar xt;
2970 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002971 // zero roots are returned only when y0 == y
2972 // Need [0] if dir == 1
2973 // and [2] if dir == -1
2974 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 } else {
2976 SkScalar t = roots[0];
2977 SkScalar C = pts[0].fX;
2978 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2979 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002980 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002981 }
caryclark9aacd902015-12-14 08:38:09 -08002982 if (SkScalarNearlyEqual(xt, x)) {
2983 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2984 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002985 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002986 }
2987 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 return xt < x ? dir : 0;
2989}
2990
caryclark9aacd902015-12-14 08:38:09 -08002991static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 SkPoint dst[5];
2993 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002994
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2996 n = SkChopQuadAtYExtrema(pts, dst);
2997 pts = dst;
2998 }
caryclark9aacd902015-12-14 08:38:09 -08002999 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003001 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 }
3003 return w;
3004}
3005
caryclark9aacd902015-12-14 08:38:09 -08003006static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 SkScalar x0 = pts[0].fX;
3008 SkScalar y0 = pts[0].fY;
3009 SkScalar x1 = pts[1].fX;
3010 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003011
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003013
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003014 int dir = 1;
3015 if (y0 > y1) {
3016 SkTSwap(y0, y1);
3017 dir = -1;
3018 }
caryclark9aacd902015-12-14 08:38:09 -08003019 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 return 0;
3021 }
caryclark9cb5d752015-12-18 04:35:24 -08003022 if (checkOnCurve(x, y, pts[0], pts[1])) {
3023 *onCurveCount += 1;
3024 return 0;
3025 }
3026 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003027 return 0;
3028 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003029 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003030
caryclark9aacd902015-12-14 08:38:09 -08003031 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003032 // zero cross means the point is on the line, and since the case where
3033 // y of the query point is at the end point is handled above, we can be
3034 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003035 if (x != x1 || y != pts[1].fY) {
3036 *onCurveCount += 1;
3037 }
caryclark9aacd902015-12-14 08:38:09 -08003038 dir = 0;
3039 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003040 dir = 0;
3041 }
3042 return dir;
3043}
3044
caryclark9aacd902015-12-14 08:38:09 -08003045static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3046 SkTDArray<SkVector>* tangents) {
3047 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3048 && !between(pts[2].fY, y, pts[3].fY)) {
3049 return;
3050 }
3051 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3052 && !between(pts[2].fX, x, pts[3].fX)) {
3053 return;
3054 }
3055 SkPoint dst[10];
3056 int n = SkChopCubicAtYExtrema(pts, dst);
3057 for (int i = 0; i <= n; ++i) {
3058 SkPoint* c = &dst[i * 3];
3059 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003060 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003061 continue;
mbarbella276e6332016-05-31 14:44:01 -07003062 }
caryclark9aacd902015-12-14 08:38:09 -08003063 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3064 if (!SkScalarNearlyEqual(x, xt)) {
3065 continue;
3066 }
3067 SkVector tangent;
3068 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3069 tangents->push(tangent);
3070 }
3071}
3072
3073static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3074 SkTDArray<SkVector>* tangents) {
3075 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3076 return;
3077 }
3078 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3079 return;
3080 }
3081 SkScalar roots[2];
3082 SkScalar A = pts[2].fY;
3083 SkScalar B = pts[1].fY * w - y * w + y;
3084 SkScalar C = pts[0].fY;
3085 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3086 B -= C; // B = b*w - w * yCept + yCept - a
3087 C -= y;
3088 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3089 for (int index = 0; index < n; ++index) {
3090 SkScalar t = roots[index];
3091 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3092 if (!SkScalarNearlyEqual(x, xt)) {
3093 continue;
3094 }
3095 SkConic conic(pts, w);
3096 tangents->push(conic.evalTangentAt(t));
3097 }
3098}
3099
3100static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3101 SkTDArray<SkVector>* tangents) {
3102 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3103 return;
3104 }
3105 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3106 return;
3107 }
3108 SkScalar roots[2];
3109 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3110 2 * (pts[1].fY - pts[0].fY),
3111 pts[0].fY - y,
3112 roots);
3113 for (int index = 0; index < n; ++index) {
3114 SkScalar t = roots[index];
3115 SkScalar C = pts[0].fX;
3116 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3117 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003118 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003119 if (!SkScalarNearlyEqual(x, xt)) {
3120 continue;
3121 }
3122 tangents->push(SkEvalQuadTangentAt(pts, t));
3123 }
3124}
3125
3126static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3127 SkTDArray<SkVector>* tangents) {
3128 SkScalar y0 = pts[0].fY;
3129 SkScalar y1 = pts[1].fY;
3130 if (!between(y0, y, y1)) {
3131 return;
3132 }
3133 SkScalar x0 = pts[0].fX;
3134 SkScalar x1 = pts[1].fX;
3135 if (!between(x0, x, x1)) {
3136 return;
3137 }
3138 SkScalar dx = x1 - x0;
3139 SkScalar dy = y1 - y0;
3140 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3141 return;
3142 }
3143 SkVector v;
3144 v.set(dx, dy);
3145 tangents->push(v);
3146}
3147
reed@google.com4db592c2013-10-30 17:39:43 +00003148static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3149 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3150}
3151
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003152bool SkPath::contains(SkScalar x, SkScalar y) const {
3153 bool isInverse = this->isInverseFillType();
3154 if (this->isEmpty()) {
3155 return isInverse;
3156 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003157
reed@google.com4db592c2013-10-30 17:39:43 +00003158 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 return isInverse;
3160 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003161
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003162 SkPath::Iter iter(*this, true);
3163 bool done = false;
3164 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003165 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003166 do {
3167 SkPoint pts[4];
3168 switch (iter.next(pts, false)) {
3169 case SkPath::kMove_Verb:
3170 case SkPath::kClose_Verb:
3171 break;
3172 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003173 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 break;
3175 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003176 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003178 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003179 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003180 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003181 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003182 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003183 break;
3184 case SkPath::kDone_Verb:
3185 done = true;
3186 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003187 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003189 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3190 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3191 if (evenOddFill) {
3192 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003193 }
caryclark9aacd902015-12-14 08:38:09 -08003194 if (w) {
3195 return !isInverse;
3196 }
3197 if (onCurveCount <= 1) {
3198 return SkToBool(onCurveCount) ^ isInverse;
3199 }
3200 if ((onCurveCount & 1) || evenOddFill) {
3201 return SkToBool(onCurveCount & 1) ^ isInverse;
3202 }
halcanary9d524f22016-03-29 09:03:52 -07003203 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003204 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3205 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003206 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003207 SkTDArray<SkVector> tangents;
3208 do {
3209 SkPoint pts[4];
3210 int oldCount = tangents.count();
3211 switch (iter.next(pts, false)) {
3212 case SkPath::kMove_Verb:
3213 case SkPath::kClose_Verb:
3214 break;
3215 case SkPath::kLine_Verb:
3216 tangent_line(pts, x, y, &tangents);
3217 break;
3218 case SkPath::kQuad_Verb:
3219 tangent_quad(pts, x, y, &tangents);
3220 break;
3221 case SkPath::kConic_Verb:
3222 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3223 break;
3224 case SkPath::kCubic_Verb:
3225 tangent_cubic(pts, x, y, &tangents);
3226 break;
3227 case SkPath::kDone_Verb:
3228 done = true;
3229 break;
3230 }
3231 if (tangents.count() > oldCount) {
3232 int last = tangents.count() - 1;
3233 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003234 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003235 tangents.remove(last);
3236 } else {
3237 for (int index = 0; index < last; ++index) {
3238 const SkVector& test = tangents[index];
3239 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003240 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3241 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003242 tangents.remove(last);
3243 tangents.removeShuffle(index);
3244 break;
3245 }
3246 }
3247 }
3248 }
3249 } while (!done);
3250 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003251}
fmalitaaa0df4e2015-12-01 09:13:23 -08003252
3253int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3254 SkScalar w, SkPoint pts[], int pow2) {
3255 const SkConic conic(p0, p1, p2, w);
3256 return conic.chopIntoQuadsPOW2(pts, pow2);
3257}
bsalomonedc743a2016-06-01 09:42:31 -07003258
3259bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3260 unsigned* start) {
3261 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3262 return false;
3263 }
3264 SkPath::RawIter iter(path);
3265 SkPoint verbPts[4];
3266 SkPath::Verb v;
3267 SkPoint rectPts[5];
3268 int rectPtCnt = 0;
3269 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3270 switch (v) {
3271 case SkPath::kMove_Verb:
3272 if (0 != rectPtCnt) {
3273 return false;
3274 }
3275 rectPts[0] = verbPts[0];
3276 ++rectPtCnt;
3277 break;
3278 case SkPath::kLine_Verb:
3279 if (5 == rectPtCnt) {
3280 return false;
3281 }
3282 rectPts[rectPtCnt] = verbPts[1];
3283 ++rectPtCnt;
3284 break;
3285 case SkPath::kClose_Verb:
3286 if (4 == rectPtCnt) {
3287 rectPts[4] = rectPts[0];
3288 rectPtCnt = 5;
3289 }
3290 break;
3291 default:
3292 return false;
3293 }
3294 }
3295 if (rectPtCnt < 5) {
3296 return false;
3297 }
3298 if (rectPts[0] != rectPts[4]) {
3299 return false;
3300 }
bsalomon057ae8a2016-07-24 05:37:26 -07003301 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3302 // and pts 1 and 2 the opposite vertical or horizontal edge).
3303 bool vec03IsVertical;
3304 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3305 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3306 // Make sure it has non-zero width and height
3307 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003308 return false;
3309 }
bsalomon057ae8a2016-07-24 05:37:26 -07003310 vec03IsVertical = true;
3311 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3312 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3313 // Make sure it has non-zero width and height
3314 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3315 return false;
3316 }
3317 vec03IsVertical = false;
3318 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003319 return false;
3320 }
bsalomon057ae8a2016-07-24 05:37:26 -07003321 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3322 // set if it is on the bottom edge.
3323 unsigned sortFlags =
3324 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3325 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3326 switch (sortFlags) {
3327 case 0b00:
3328 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3329 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3330 *start = 0;
3331 break;
3332 case 0b01:
3333 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3334 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3335 *start = 1;
3336 break;
3337 case 0b10:
3338 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3339 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3340 *start = 3;
3341 break;
3342 case 0b11:
3343 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3344 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3345 *start = 2;
3346 break;
bsalomonedc743a2016-06-01 09:42:31 -07003347 }
bsalomonedc743a2016-06-01 09:42:31 -07003348 return true;
3349}
bsalomon21af9ca2016-08-25 12:29:23 -07003350
3351void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3352 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3353 SkASSERT(!oval.isEmpty());
3354 SkASSERT(sweepAngle);
3355
3356 path->reset();
3357 path->setIsVolatile(true);
3358 path->setFillType(SkPath::kWinding_FillType);
3359 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3360 path->addOval(oval);
3361 return;
3362 }
3363 if (useCenter) {
3364 path->moveTo(oval.centerX(), oval.centerY());
3365 }
3366 // Arc to mods at 360 and drawArc is not supposed to.
3367 bool forceMoveTo = !useCenter;
3368 while (sweepAngle <= -360.f) {
3369 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3370 startAngle -= 180.f;
3371 path->arcTo(oval, startAngle, -180.f, false);
3372 startAngle -= 180.f;
3373 forceMoveTo = false;
3374 sweepAngle += 360.f;
3375 }
3376 while (sweepAngle >= 360.f) {
3377 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3378 startAngle += 180.f;
3379 path->arcTo(oval, startAngle, 180.f, false);
3380 startAngle += 180.f;
3381 forceMoveTo = false;
3382 sweepAngle -= 360.f;
3383 }
3384 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3385 if (useCenter) {
3386 path->close();
3387 }
3388}
Mike Reed0d7dac82017-02-02 17:45:56 -08003389
3390///////////////////////////////////////////////////////////////////////////////////////////////////
3391#include "SkNx.h"
3392
3393static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3394 SkScalar ts[2];
3395 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3396 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3397 SkASSERT(n >= 0 && n <= 2);
3398 for (int i = 0; i < n; ++i) {
3399 extremas[i] = SkEvalQuadAt(src, ts[i]);
3400 }
3401 extremas[n] = src[2];
3402 return n + 1;
3403}
3404
3405static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3406 SkConic conic(src[0], src[1], src[2], w);
3407 SkScalar ts[2];
3408 int n = conic.findXExtrema(ts);
3409 n += conic.findYExtrema(&ts[n]);
3410 SkASSERT(n >= 0 && n <= 2);
3411 for (int i = 0; i < n; ++i) {
3412 extremas[i] = conic.evalAt(ts[i]);
3413 }
3414 extremas[n] = src[2];
3415 return n + 1;
3416}
3417
3418static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3419 SkScalar ts[4];
3420 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3421 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3422 SkASSERT(n >= 0 && n <= 4);
3423 for (int i = 0; i < n; ++i) {
3424 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3425 }
3426 extremas[n] = src[3];
3427 return n + 1;
3428}
3429
Mike Reed8d3196b2017-02-03 11:34:13 -05003430SkRect SkPath::computeTightBounds() const {
3431 if (0 == this->countVerbs()) {
3432 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003433 }
3434
Mike Reed8d3196b2017-02-03 11:34:13 -05003435 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3436 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003437 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003438
Mike Reed0d7dac82017-02-02 17:45:56 -08003439 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3440 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003441 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003442
3443 // initial with the first MoveTo, so we don't have to check inside the switch
3444 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003445 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003446 for (;;) {
3447 int count = 0;
3448 switch (iter.next(pts)) {
3449 case SkPath::kMove_Verb:
3450 extremas[0] = pts[0];
3451 count = 1;
3452 break;
3453 case SkPath::kLine_Verb:
3454 extremas[0] = pts[1];
3455 count = 1;
3456 break;
3457 case SkPath::kQuad_Verb:
3458 count = compute_quad_extremas(pts, extremas);
3459 break;
3460 case SkPath::kConic_Verb:
3461 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3462 break;
3463 case SkPath::kCubic_Verb:
3464 count = compute_cubic_extremas(pts, extremas);
3465 break;
3466 case SkPath::kClose_Verb:
3467 break;
3468 case SkPath::kDone_Verb:
3469 goto DONE;
3470 }
3471 for (int i = 0; i < count; ++i) {
3472 Sk2s tmp = from_point(extremas[i]);
3473 min = Sk2s::Min(min, tmp);
3474 max = Sk2s::Max(max, tmp);
3475 }
3476 }
3477DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003478 SkRect bounds;
3479 min.store((SkPoint*)&bounds.fLeft);
3480 max.store((SkPoint*)&bounds.fRight);
3481 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003482}
Cary Clarkdf429f32017-11-08 11:44:31 -05003483
3484bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3485 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3486}
3487
3488bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3489 const SkPoint& p3, bool exact) {
3490 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3491 SkPointPriv::EqualsWithinTolerance(p2, p3);
3492}
3493
3494bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3495 const SkPoint& p3, const SkPoint& p4, bool exact) {
3496 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3497 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3498 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3499 SkPointPriv::EqualsWithinTolerance(p3, p4);
3500}