blob: 05d2ca8586cd54b36128b82a08553773c5d412c7 [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
Hal Canaryc640d0d2018-06-13 09:59:02 -04008#include "SkPath.h"
9
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080011#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040012#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080013#include "SkGeometry.h"
Hal Canary22be4c42018-06-12 12:37:31 -040014#include "SkMacros.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040016#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070017#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000018#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050019#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000020#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050021#include "SkSafeMath.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040022#include "SkTo.h"
23
24#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040025#include <utility>
reed@android.com8a1c16f2008-12-17 15:59:43 +000026
Mike Reeda99b6ce2017-02-04 11:04:26 -050027static float poly_eval(float A, float B, float C, float t) {
28 return (A * t + B) * t + C;
29}
30
31static float poly_eval(float A, float B, float C, float D, float t) {
32 return ((A * t + B) * t + C) * t + D;
33}
34
reed@android.com8a1c16f2008-12-17 15:59:43 +000035////////////////////////////////////////////////////////////////////////////
36
reed@google.com3563c9e2011-11-14 19:34:57 +000037/**
38 * Path.bounds is defined to be the bounds of all the control points.
39 * If we called bounds.join(r) we would skip r if r was empty, which breaks
40 * our promise. Hence we have a custom joiner that doesn't look at emptiness
41 */
42static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
43 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
44 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
45 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
46 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
47}
48
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000049static bool is_degenerate(const SkPath& path) {
50 SkPath::Iter iter(path, false);
51 SkPoint pts[4];
52 return SkPath::kDone_Verb == iter.next(pts);
53}
54
bsalomon@google.com30c174b2012-11-13 14:36:42 +000055class SkAutoDisableDirectionCheck {
56public:
57 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070058 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000059 }
60
61 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070062 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000063 }
64
65private:
reed026beb52015-06-10 14:23:15 -070066 SkPath* fPath;
67 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000068};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000069#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000070
reed@android.com8a1c16f2008-12-17 15:59:43 +000071/* This guy's constructor/destructor bracket a path editing operation. It is
72 used when we know the bounds of the amount we are going to add to the path
73 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000074
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000076 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000077 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000078
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000079 It also notes if the path was originally degenerate, and if so, sets
80 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000081 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 */
83class SkAutoPathBoundsUpdate {
84public:
85 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
86 this->init(path);
87 }
88
89 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
90 SkScalar right, SkScalar bottom) {
91 fRect.set(left, top, right, bottom);
92 this->init(path);
93 }
reed@google.comabf15c12011-01-18 20:35:51 +000094
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000096 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
97 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050098 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 }
101 }
reed@google.comabf15c12011-01-18 20:35:51 +0000102
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 SkPath* fPath;
105 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000107 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000108 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000109
reed@android.com6b82d1a2009-06-03 02:35:01 +0000110 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000111 // Cannot use fRect for our bounds unless we know it is sorted
112 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000114 // Mark the path's bounds as dirty if (1) they are, or (2) the path
115 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000116 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000118 if (fHasValidBounds && !fEmpty) {
119 joinNoEmptyChecks(&fRect, fPath->getBounds());
120 }
121 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 }
123};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000124#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126////////////////////////////////////////////////////////////////////////////
127
128/*
129 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000130 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
132
133 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000134 1. If we encounter degenerate segments, remove them
135 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
136 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
137 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138*/
139
140////////////////////////////////////////////////////////////////////////////
141
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000142// flag to require a moveTo if we begin with something else, like lineTo etc.
143#define INITIAL_LASTMOVETOINDEX_VALUE ~0
144
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000145SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800146 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700148 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400149 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150}
151
152void SkPath::resetFields() {
153 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000154 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000156 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700157 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000158
159 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700160 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000161}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162
bungeman@google.coma5809a32013-06-21 15:13:34 +0000163SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000164 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 this->copyFields(that);
166 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167}
168
169SkPath::~SkPath() {
170 SkDEBUGCODE(this->validate();)
171}
172
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173SkPath& SkPath::operator=(const SkPath& that) {
174 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 if (this != &that) {
177 fPathRef.reset(SkRef(that.fPathRef.get()));
178 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 }
180 SkDEBUGCODE(this->validate();)
181 return *this;
182}
183
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184void SkPath::copyFields(const SkPath& that) {
185 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000186 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700188 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400189 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400190
191 // Non-atomic assignment of atomic values.
192 fConvexity .store(that.fConvexity .load());
193 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194}
195
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000196bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000197 // note: don't need to look at isConvex or bounds, since just comparing the
198 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000199 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000200 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000201}
202
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 if (this != &that) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400205 using std::swap;
bungeman77a53de2015-10-01 12:28:49 -0700206 fPathRef.swap(that.fPathRef);
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400207 swap(fLastMoveToIndex, that.fLastMoveToIndex);
208 swap(fFillType, that.fFillType);
209 swap(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400210
211 // Non-atomic swaps of atomic values.
212 Convexity c = fConvexity.load();
213 fConvexity.store(that.fConvexity.load());
214 that.fConvexity.store(c);
215
216 uint8_t fd = fFirstDirection.load();
217 fFirstDirection.store(that.fFirstDirection.load());
218 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 }
220}
221
caryclark8e7b19d2016-02-18 04:11:48 -0800222bool SkPath::isInterpolatable(const SkPath& compare) const {
223 int count = fPathRef->countVerbs();
224 if (count != compare.fPathRef->countVerbs()) {
225 return false;
226 }
227 if (!count) {
228 return true;
229 }
230 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
231 count)) {
232 return false;
233 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800234 return !fPathRef->countWeights() ||
235 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800236 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
237}
238
239bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500240 int pointCount = fPathRef->countPoints();
241 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800242 return false;
243 }
Cary Clark7487ec82018-03-06 09:30:46 -0500244 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800245 return true;
246 }
caryclark8e7b19d2016-02-18 04:11:48 -0800247 out->reset();
248 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700249 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800250 return true;
251}
252
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253static inline bool check_edge_against_rect(const SkPoint& p0,
254 const SkPoint& p1,
255 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700256 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 const SkPoint* edgeBegin;
258 SkVector v;
reed026beb52015-06-10 14:23:15 -0700259 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 v = p1 - p0;
261 edgeBegin = &p0;
262 } else {
263 v = p0 - p1;
264 edgeBegin = &p1;
265 }
266 if (v.fX || v.fY) {
267 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500268 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
269 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
270 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
271 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
273 return false;
274 }
275 }
276 return true;
277}
278
279bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
280 // This only handles non-degenerate convex paths currently.
281 if (kConvex_Convexity != this->getConvexity()) {
282 return false;
283 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000284
reed026beb52015-06-10 14:23:15 -0700285 SkPathPriv::FirstDirection direction;
286 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 return false;
288 }
289
290 SkPoint firstPt;
291 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 SkPath::Verb verb;
294 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400295 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000296 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298
Lee Salzmana19f0242017-01-12 13:06:21 -0500299 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 int nextPt = -1;
301 switch (verb) {
302 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 SkDEBUGCODE(++moveCnt);
305 firstPt = prevPt = pts[0];
306 break;
307 case kLine_Verb:
308 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000309 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400310 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311 break;
312 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000313 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000314 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400315 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000316 nextPt = 2;
317 break;
318 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000319 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400320 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000321 nextPt = 3;
322 break;
323 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000324 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000325 break;
326 default:
327 SkDEBUGFAIL("unknown verb");
328 }
329 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800330 if (SkPath::kConic_Verb == verb) {
331 SkConic orig;
332 orig.set(pts, iter.conicWeight());
333 SkPoint quadPts[5];
334 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800335 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800336
337 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
338 return false;
339 }
340 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
341 return false;
342 }
343 } else {
344 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
345 return false;
346 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000347 }
348 prevPt = pts[nextPt];
349 }
350 }
351
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400352 if (segmentCount) {
353 return check_edge_against_rect(prevPt, firstPt, rect, direction);
354 }
355 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000356}
357
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000358uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000359 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800360#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400361 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
362 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000363#endif
364 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000365}
djsollen@google.come63793a2012-03-21 15:39:03 +0000366
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367void SkPath::reset() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
374void SkPath::rewind() {
375 SkDEBUGCODE(this->validate();)
376
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000377 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000378 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
fsb1475b02016-01-20 09:51:07 -0800381bool SkPath::isLastContourClosed() const {
382 int verbCount = fPathRef->countVerbs();
383 if (0 == verbCount) {
384 return false;
385 }
386 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
387}
388
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000391
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000392 if (2 == verbCount) {
393 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
394 if (kLine_Verb == fPathRef->atVerb(1)) {
395 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000396 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000397 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000398 line[0] = pts[0];
399 line[1] = pts[1];
400 }
401 return true;
402 }
403 }
404 return false;
405}
406
caryclark@google.comf1316942011-07-26 19:54:45 +0000407/*
408 Determines if path is a rect by keeping track of changes in direction
409 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000410
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 The direction is computed such that:
412 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000413 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000415 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000416
caryclark@google.comf1316942011-07-26 19:54:45 +0000417A rectangle cycles up/right/down/left or up/left/down/right.
418
419The test fails if:
420 The path is closed, and followed by a line.
421 A second move creates a new endpoint.
422 A diagonal line is parsed.
423 There's more than four changes of direction.
424 There's a discontinuity on the line (e.g., a move in the middle)
425 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000426 The path contains a quadratic or cubic.
427 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000428 *The rectangle doesn't complete a cycle.
429 *The final point isn't equal to the first point.
430
431 *These last two conditions we relax if we have a 3-edge path that would
432 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000433
caryclark@google.comf1316942011-07-26 19:54:45 +0000434It's OK if the path has:
435 Several colinear line segments composing a rectangle side.
436 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000437
caryclark@google.comf1316942011-07-26 19:54:45 +0000438The direction takes advantage of the corners found since opposite sides
439must travel in opposite directions.
440
441FIXME: Allow colinear quads and cubics to be treated like lines.
442FIXME: If the API passes fill-only, return true if the filled stroke
443 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000444
Cary Clark48c464a2018-04-16 12:06:07 -0400445 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446 0x1 is set if the segment is horizontal
447 0x2 is set if the segment is moving to the right or down
448 thus:
449 two directions are opposites iff (dirA ^ dirB) == 0x2
450 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000451
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000453static int rect_make_dir(SkScalar dx, SkScalar dy) {
454 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
455}
Cary Clark88ba9712018-04-12 14:00:24 -0400456
caryclark@google.comf68154a2012-11-21 15:18:06 +0000457bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400458 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400460 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400461 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400462 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
463 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400464 SkPoint firstCorner;
465 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000466 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400467 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400468 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400469 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000470 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700472 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000473 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000474 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700475 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
476 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000478 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700480 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400482 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400483 lastPt = pts;
484 }
Cary Clark88ba9712018-04-12 14:00:24 -0400485 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
486 SkVector lineDelta = lineEnd - lineStart;
487 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 return false; // diagonal
489 }
Cary Clark1eece782018-04-20 10:14:41 -0400490 if (!lineDelta.isFinite()) {
491 return false; // path contains infinity or NaN
492 }
Cary Clark88ba9712018-04-12 14:00:24 -0400493 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 break; // single point on side OK
495 }
Cary Clark48c464a2018-04-16 12:06:07 -0400496 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000497 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400498 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000499 corners = 1;
500 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400501 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 break;
503 }
504 if (closedOrMoved) {
505 return false; // closed followed by a line
506 }
Cary Clark48c464a2018-04-16 12:06:07 -0400507 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000508 break; // colinear with first
509 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400511 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400512 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400513 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400514 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400515 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 break; // colinear segment
517 }
Cary Clark48c464a2018-04-16 12:06:07 -0400518 directions[corners++] = nextDirection;
519 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400520 switch (corners) {
521 case 2:
522 firstCorner = lineStart;
523 break;
524 case 3:
525 if ((directions[0] ^ directions[2]) != 2) {
526 return false;
527 }
528 thirdCorner = lineEnd;
529 break;
530 case 4:
531 if ((directions[1] ^ directions[3]) != 2) {
532 return false;
533 }
534 break;
535 default:
536 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400538 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000539 break;
540 }
541 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000542 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000543 case kCubic_Verb:
544 return false; // quadratic, cubic not allowed
545 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400546 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700547 insertClose = true;
548 *currVerb -= 1; // try move again afterwards
549 goto addMissingClose;
550 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400551 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400552 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400553 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400554 closeXY = *firstPt - *lastPt;
555 if (closeXY.fX && closeXY.fY) {
556 return false; // we're diagonal, abort
557 }
Cary Clark8540e112018-04-11 14:30:27 -0400558 }
Cary Clark88ba9712018-04-12 14:00:24 -0400559 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 closedOrMoved = true;
561 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000562 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000563 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000564 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000565 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000566 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700567addMissingClose:
568 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000569 }
570 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400571 if (corners < 3 || corners > 4) {
572 return false;
573 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000574 if (savePts) {
575 *ptsPtr = savePts;
576 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400577 // check if close generates diagonal
578 closeXY = *firstPt - *lastPt;
579 if (closeXY.fX && closeXY.fY) {
580 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400581 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400582 if (rect) {
583 rect->set(firstCorner, thirdCorner);
584 }
585 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000586 *isClosed = autoClose;
587 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400588 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400589 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000590 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400591 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592}
593
robertphillips4f662e62014-12-29 14:06:51 -0800594bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 SkDEBUGCODE(this->validate();)
596 int currVerb = 0;
597 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400598 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000599}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000600
caryclark95bc5f32015-04-08 08:34:15 -0700601bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602 SkDEBUGCODE(this->validate();)
603 int currVerb = 0;
604 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000605 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400606 SkRect testRects[2];
607 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 return false;
609 }
Cary Clark5c715182018-04-09 16:07:11 -0400610 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 if (testRects[0].contains(testRects[1])) {
612 if (rects) {
613 rects[0] = testRects[0];
614 rects[1] = testRects[1];
615 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000616 if (dirs) {
617 dirs[0] = testDirs[0];
618 dirs[1] = testDirs[1];
619 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000620 return true;
621 }
622 if (testRects[1].contains(testRects[0])) {
623 if (rects) {
624 rects[0] = testRects[1];
625 rects[1] = testRects[0];
626 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000627 if (dirs) {
628 dirs[0] = testDirs[1];
629 dirs[1] = testDirs[0];
630 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000631 return true;
632 }
633 }
634 return false;
635}
636
Mike Reed0c3137c2018-02-20 13:57:05 -0500637bool SkPath::isOval(SkRect* bounds) const {
638 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
639}
640
641bool SkPath::isRRect(SkRRect* rrect) const {
642 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
643}
644
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000645int SkPath::countPoints() const {
646 return fPathRef->countPoints();
647}
648
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000649int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkDEBUGCODE(this->validate();)
651
652 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000653 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800655 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657}
658
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000659SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
661 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000662 }
663 return SkPoint::Make(0, 0);
664}
665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666int SkPath::countVerbs() const {
667 return fPathRef->countVerbs();
668}
669
670static inline void copy_verbs_reverse(uint8_t* inorderDst,
671 const uint8_t* reversedSrc,
672 int count) {
673 for (int i = 0; i < count; ++i) {
674 inorderDst[i] = reversedSrc[~i];
675 }
676}
677
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000678int SkPath::getVerbs(uint8_t dst[], int max) const {
679 SkDEBUGCODE(this->validate();)
680
681 SkASSERT(max >= 0);
682 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 int count = SkMin32(max, fPathRef->countVerbs());
684 copy_verbs_reverse(dst, fPathRef->verbs(), count);
685 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000686}
687
reed@google.com294dd7b2011-10-11 11:58:32 +0000688bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 SkDEBUGCODE(this->validate();)
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000692 if (count > 0) {
693 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000698 if (lastPt) {
699 lastPt->set(0, 0);
700 }
701 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000702}
703
caryclarkaec25102015-04-29 08:28:30 -0700704void SkPath::setPt(int index, SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
707 int count = fPathRef->countPoints();
708 if (count <= index) {
709 return;
710 } else {
711 SkPathRef::Editor ed(&fPathRef);
712 ed.atPoint(index)->set(x, y);
713 }
714}
715
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716void SkPath::setLastPt(SkScalar x, SkScalar y) {
717 SkDEBUGCODE(this->validate();)
718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 if (count == 0) {
721 this->moveTo(x, y);
722 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
724 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 }
726}
727
reed@google.com04863fa2011-05-15 04:08:24 +0000728void SkPath::setConvexity(Convexity c) {
729 if (fConvexity != c) {
730 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000731 }
732}
733
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734//////////////////////////////////////////////////////////////////////////////
735// Construction methods
736
reed026beb52015-06-10 14:23:15 -0700737#define DIRTY_AFTER_EDIT \
738 do { \
739 fConvexity = kUnknown_Convexity; \
740 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000741 } while (0)
742
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743void SkPath::incReserve(U16CPU inc) {
744 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 SkDEBUGCODE(this->validate();)
747}
748
749void SkPath::moveTo(SkScalar x, SkScalar y) {
750 SkDEBUGCODE(this->validate();)
751
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000752 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000754 // remember our index
755 fLastMoveToIndex = fPathRef->countPoints();
756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000757 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700758
759 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760}
761
762void SkPath::rMoveTo(SkScalar x, SkScalar y) {
763 SkPoint pt;
764 this->getLastPt(&pt);
765 this->moveTo(pt.fX + x, pt.fY + y);
766}
767
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000768void SkPath::injectMoveToIfNeeded() {
769 if (fLastMoveToIndex < 0) {
770 SkScalar x, y;
771 if (fPathRef->countVerbs() == 0) {
772 x = y = 0;
773 } else {
774 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
775 x = pt.fX;
776 y = pt.fY;
777 }
778 this->moveTo(x, y);
779 }
780}
781
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782void SkPath::lineTo(SkScalar x, SkScalar y) {
783 SkDEBUGCODE(this->validate();)
784
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000785 this->injectMoveToIfNeeded();
786
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000787 SkPathRef::Editor ed(&fPathRef);
788 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789
reed@google.comb54455e2011-05-16 14:16:04 +0000790 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791}
792
793void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000794 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 SkPoint pt;
796 this->getLastPt(&pt);
797 this->lineTo(pt.fX + x, pt.fY + y);
798}
799
800void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
801 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000802
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000803 this->injectMoveToIfNeeded();
804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000805 SkPathRef::Editor ed(&fPathRef);
806 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 pts[0].set(x1, y1);
808 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000809
reed@google.comb54455e2011-05-16 14:16:04 +0000810 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811}
812
813void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000814 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 SkPoint pt;
816 this->getLastPt(&pt);
817 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
818}
819
reed@google.com277c3f82013-05-31 15:17:50 +0000820void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
821 SkScalar w) {
822 // check for <= 0 or NaN with this test
823 if (!(w > 0)) {
824 this->lineTo(x2, y2);
825 } else if (!SkScalarIsFinite(w)) {
826 this->lineTo(x1, y1);
827 this->lineTo(x2, y2);
828 } else if (SK_Scalar1 == w) {
829 this->quadTo(x1, y1, x2, y2);
830 } else {
831 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000832
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000833 this->injectMoveToIfNeeded();
834
reed@google.com277c3f82013-05-31 15:17:50 +0000835 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000836 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000837 pts[0].set(x1, y1);
838 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000839
reed@google.com277c3f82013-05-31 15:17:50 +0000840 DIRTY_AFTER_EDIT;
841 }
842}
843
844void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
845 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000846 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000847 SkPoint pt;
848 this->getLastPt(&pt);
849 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
850}
851
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
853 SkScalar x3, SkScalar y3) {
854 SkDEBUGCODE(this->validate();)
855
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000856 this->injectMoveToIfNeeded();
857
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000858 SkPathRef::Editor ed(&fPathRef);
859 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 pts[0].set(x1, y1);
861 pts[1].set(x2, y2);
862 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863
reed@google.comb54455e2011-05-16 14:16:04 +0000864 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865}
866
867void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
868 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000869 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 SkPoint pt;
871 this->getLastPt(&pt);
872 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
873 pt.fX + x3, pt.fY + y3);
874}
875
876void SkPath::close() {
877 SkDEBUGCODE(this->validate();)
878
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 case kLine_Verb:
883 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000884 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000885 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000886 case kMove_Verb: {
887 SkPathRef::Editor ed(&fPathRef);
888 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000890 }
reed@google.com277c3f82013-05-31 15:17:50 +0000891 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000892 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000893 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000894 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000895 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000896 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 }
898 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000899
900 // signal that we need a moveTo to follow us (unless we're done)
901#if 0
902 if (fLastMoveToIndex >= 0) {
903 fLastMoveToIndex = ~fLastMoveToIndex;
904 }
905#else
906 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
907#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908}
909
910///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000911
fmalitac08d53e2015-11-17 09:53:29 -0800912namespace {
913
914template <unsigned N>
915class PointIterator {
916public:
917 PointIterator(SkPath::Direction dir, unsigned startIndex)
918 : fCurrent(startIndex % N)
919 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
920
921 const SkPoint& current() const {
922 SkASSERT(fCurrent < N);
923 return fPts[fCurrent];
924 }
925
926 const SkPoint& next() {
927 fCurrent = (fCurrent + fAdvance) % N;
928 return this->current();
929 }
930
931protected:
932 SkPoint fPts[N];
933
934private:
935 unsigned fCurrent;
936 unsigned fAdvance;
937};
938
939class RectPointIterator : public PointIterator<4> {
940public:
941 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
942 : PointIterator(dir, startIndex) {
943
944 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
945 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
946 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
947 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
948 }
949};
950
951class OvalPointIterator : public PointIterator<4> {
952public:
953 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
954 : PointIterator(dir, startIndex) {
955
956 const SkScalar cx = oval.centerX();
957 const SkScalar cy = oval.centerY();
958
959 fPts[0] = SkPoint::Make(cx, oval.fTop);
960 fPts[1] = SkPoint::Make(oval.fRight, cy);
961 fPts[2] = SkPoint::Make(cx, oval.fBottom);
962 fPts[3] = SkPoint::Make(oval.fLeft, cy);
963 }
964};
965
966class RRectPointIterator : public PointIterator<8> {
967public:
968 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
969 : PointIterator(dir, startIndex) {
970
971 const SkRect& bounds = rrect.getBounds();
972 const SkScalar L = bounds.fLeft;
973 const SkScalar T = bounds.fTop;
974 const SkScalar R = bounds.fRight;
975 const SkScalar B = bounds.fBottom;
976
977 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
978 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
979 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
980 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
981 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
982 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
983 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
984 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
985 }
986};
987
988} // anonymous namespace
989
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000990static void assert_known_direction(int dir) {
991 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
992}
993
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800995 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996}
997
998void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
999 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001000 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
1001}
1002
1003void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001004 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001005 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001006 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001007 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001008 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011
fmalitac08d53e2015-11-17 09:53:29 -08001012 const int kVerbs = 5; // moveTo + 3x lineTo + close
1013 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014
fmalitac08d53e2015-11-17 09:53:29 -08001015 RectPointIterator iter(rect, dir, startIndex);
1016
1017 this->moveTo(iter.current());
1018 this->lineTo(iter.next());
1019 this->lineTo(iter.next());
1020 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001022
1023 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001024}
1025
reed@google.com744faba2012-05-29 19:54:52 +00001026void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1027 SkDEBUGCODE(this->validate();)
1028 if (count <= 0) {
1029 return;
1030 }
1031
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001032 fLastMoveToIndex = fPathRef->countPoints();
1033
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001034 // +close makes room for the extra kClose_Verb
1035 SkPathRef::Editor ed(&fPathRef, count+close, count);
1036
1037 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001038 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001039 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1040 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001041 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042
reed@google.com744faba2012-05-29 19:54:52 +00001043 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001044 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001045 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001046 }
1047
reed@google.com744faba2012-05-29 19:54:52 +00001048 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001049 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001050}
1051
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052#include "SkGeometry.h"
1053
reedf90ea012015-01-29 12:03:58 -08001054static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1055 SkPoint* pt) {
1056 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057 // Chrome uses this path to move into and out of ovals. If not
1058 // treated as a special case the moves can distort the oval's
1059 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001060 pt->set(oval.fRight, oval.centerY());
1061 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062 } else if (0 == oval.width() && 0 == oval.height()) {
1063 // Chrome will sometimes create 0 radius round rects. Having degenerate
1064 // quad segments in the path prevents the path from being recognized as
1065 // a rect.
1066 // TODO: optimizing the case where only one of width or height is zero
1067 // should also be considered. This case, however, doesn't seem to be
1068 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001069 pt->set(oval.fRight, oval.fTop);
1070 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071 }
reedf90ea012015-01-29 12:03:58 -08001072 return false;
1073}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074
reedd5d27d92015-02-09 13:54:43 -08001075// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1076//
1077static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1078 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1079 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1080 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081
1082 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001083 loss in radians-conversion and/or sin/cos, we may end up with coincident
1084 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1085 of drawing a nearly complete circle (good).
1086 e.g. canvas.drawArc(0, 359.99, ...)
1087 -vs- canvas.drawArc(0, 359.9, ...)
1088 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 */
reedd5d27d92015-02-09 13:54:43 -08001090 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091 SkScalar sw = SkScalarAbs(sweepAngle);
1092 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1093 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1094 // make a guess at a tiny angle (in radians) to tweak by
1095 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1096 // not sure how much will be enough, so we use a loop
1097 do {
1098 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001099 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1100 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001101 }
1102 }
reedd5d27d92015-02-09 13:54:43 -08001103 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1104}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001105
reed9e779d42015-02-17 11:43:14 -08001106/**
1107 * If this returns 0, then the caller should just line-to the singlePt, else it should
1108 * ignore singlePt and append the specified number of conics.
1109 */
reedd5d27d92015-02-09 13:54:43 -08001110static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001111 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1112 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001113 SkMatrix matrix;
1114
1115 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1116 matrix.postTranslate(oval.centerX(), oval.centerY());
1117
reed9e779d42015-02-17 11:43:14 -08001118 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1119 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001120 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001121 }
1122 return count;
reedd5d27d92015-02-09 13:54:43 -08001123}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001124
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001127 SkRRect rrect;
1128 rrect.setRectRadii(rect, (const SkVector*) radii);
1129 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130}
1131
reed@google.com4ed0fb72012-12-12 20:48:18 +00001132void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001133 // legacy start indices: 6 (CW) and 7(CCW)
1134 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1135}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001136
fmalitac08d53e2015-11-17 09:53:29 -08001137void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1138 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001139
caryclarkda707bf2015-11-19 14:47:43 -08001140 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001141 const SkRect& bounds = rrect.getBounds();
1142
Brian Salomon0a241ce2017-12-15 11:31:05 -05001143 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001144 // degenerate(rect) => radii points are collapsing
1145 this->addRect(bounds, dir, (startIndex + 1) / 2);
1146 } else if (rrect.isOval()) {
1147 // degenerate(oval) => line points are collapsing
1148 this->addOval(bounds, dir, startIndex / 2);
1149 } else {
1150 fFirstDirection = this->hasOnlyMoveTos() ?
1151 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1152
1153 SkAutoPathBoundsUpdate apbu(this, bounds);
1154 SkAutoDisableDirectionCheck addc(this);
1155
1156 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1157 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1158 const SkScalar weight = SK_ScalarRoot2Over2;
1159
1160 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1161 const int kVerbs = startsWithConic
1162 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1163 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1164 this->incReserve(kVerbs);
1165
1166 RRectPointIterator rrectIter(rrect, dir, startIndex);
1167 // Corner iterator indices follow the collapsed radii model,
1168 // adjusted such that the start pt is "behind" the radii start pt.
1169 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1170 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1171
1172 this->moveTo(rrectIter.current());
1173 if (startsWithConic) {
1174 for (unsigned i = 0; i < 3; ++i) {
1175 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1176 this->lineTo(rrectIter.next());
1177 }
1178 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1179 // final lineTo handled by close().
1180 } else {
1181 for (unsigned i = 0; i < 4; ++i) {
1182 this->lineTo(rrectIter.next());
1183 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1184 }
1185 }
1186 this->close();
1187
caryclarkda707bf2015-11-19 14:47:43 -08001188 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001189 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001190
fmalitac08d53e2015-11-17 09:53:29 -08001191 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1192 }
1193
1194 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001195}
1196
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001198 int count = fPathRef->countVerbs();
1199 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1200 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001201 if (*verbs == kLine_Verb ||
1202 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001203 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001204 *verbs == kCubic_Verb) {
1205 return false;
1206 }
1207 ++verbs;
1208 }
1209 return true;
1210}
1211
Brian Osmana2318572017-07-10 15:09:26 -04001212bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1213 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001214 if (count < 2) {
1215 return true;
1216 }
Brian Osmana2318572017-07-10 15:09:26 -04001217 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001218 const SkPoint& first = *pts;
1219 for (int index = 1; index < count; ++index) {
1220 if (first != pts[index]) {
1221 return false;
1222 }
1223 }
1224 return true;
1225}
1226
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001227void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1228 Direction dir) {
1229 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001230
humper@google.com75e3ca12013-04-08 21:44:11 +00001231 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001232 return;
1233 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001234
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001235 SkRRect rrect;
1236 rrect.setRectXY(rect, rx, ry);
1237 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001238}
1239
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001241 // legacy start index: 1
1242 this->addOval(oval, dir, 1);
1243}
1244
1245void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001246 assert_known_direction(dir);
1247
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001248 /* If addOval() is called after previous moveTo(),
1249 this path is still marked as an oval. This is used to
1250 fit into WebKit's calling sequences.
1251 We can't simply check isEmpty() in this case, as additional
1252 moveTo() would mark the path non empty.
1253 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001254 bool isOval = hasOnlyMoveTos();
1255 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001256 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 } else {
reed026beb52015-06-10 14:23:15 -07001258 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001260
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001261 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001262 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263
fmalitac08d53e2015-11-17 09:53:29 -08001264 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1265 const int kVerbs = 6; // moveTo + 4x conicTo + close
1266 this->incReserve(kVerbs);
1267
1268 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1269 // The corner iterator pts are tracking "behind" the oval/radii pts.
1270 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001271 const SkScalar weight = SK_ScalarRoot2Over2;
1272
fmalitac08d53e2015-11-17 09:53:29 -08001273 this->moveTo(ovalIter.current());
1274 for (unsigned i = 0; i < 4; ++i) {
1275 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001276 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278
fmalitac08d53e2015-11-17 09:53:29 -08001279 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1280
robertphillips@google.com466310d2013-12-03 16:43:54 +00001281 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282
bsalomon78d58d12016-05-27 09:17:04 -07001283 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001284}
1285
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1287 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001288 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 }
1290}
1291
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1293 bool forceMoveTo) {
1294 if (oval.width() < 0 || oval.height() < 0) {
1295 return;
1296 }
1297
reedf90ea012015-01-29 12:03:58 -08001298 if (fPathRef->countVerbs() == 0) {
1299 forceMoveTo = true;
1300 }
1301
1302 SkPoint lonePt;
1303 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1304 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1305 return;
1306 }
1307
reedd5d27d92015-02-09 13:54:43 -08001308 SkVector startV, stopV;
1309 SkRotationDirection dir;
1310 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1311
reed9e779d42015-02-17 11:43:14 -08001312 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001313
Brian Salomone4949402018-04-26 15:22:04 -04001314 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1315 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1316 // arcs from the same oval.
1317 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1318 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001319 if (forceMoveTo) {
1320 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001321 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001322 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1323 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1324 this->lineTo(pt);
1325 }
1326 };
1327
xidachen6069dda2016-10-06 05:42:23 -07001328 // At this point, we know that the arc is not a lone point, but startV == stopV
1329 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1330 // cannot handle it.
1331 if (startV == stopV) {
1332 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1333 SkScalar radiusX = oval.width() / 2;
1334 SkScalar radiusY = oval.height() / 2;
1335 // We cannot use SkScalarSinCos function in the next line because
1336 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1337 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001338 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001339 // make sin(endAngle) to be 0 which will then draw a dot.
1340 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1341 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001342 addPt(singlePt);
xidachen6069dda2016-10-06 05:42:23 -07001343 return;
1344 }
1345
reedd5d27d92015-02-09 13:54:43 -08001346 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001347 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001348 if (count) {
1349 this->incReserve(count * 2 + 1);
1350 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001351 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001352 for (int i = 0; i < count; ++i) {
1353 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1354 }
reed9e779d42015-02-17 11:43:14 -08001355 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001356 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001357 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358}
1359
caryclark55d49052016-01-23 05:07:04 -08001360// This converts the SVG arc to conics.
1361// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1362// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1363// See also SVG implementation notes:
1364// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1365// Note that arcSweep bool value is flipped from the original implementation.
1366void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1367 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001368 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001369 SkPoint srcPts[2];
1370 this->getLastPt(&srcPts[0]);
1371 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1372 // joining the endpoints.
1373 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1374 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001375 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001376 return;
1377 }
1378 // If the current point and target point for the arc are identical, it should be treated as a
1379 // zero length path. This ensures continuity in animations.
1380 srcPts[1].set(x, y);
1381 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001382 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001383 return;
1384 }
1385 rx = SkScalarAbs(rx);
1386 ry = SkScalarAbs(ry);
1387 SkVector midPointDistance = srcPts[0] - srcPts[1];
1388 midPointDistance *= 0.5f;
1389
1390 SkMatrix pointTransform;
1391 pointTransform.setRotate(-angle);
1392
1393 SkPoint transformedMidPoint;
1394 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1395 SkScalar squareRx = rx * rx;
1396 SkScalar squareRy = ry * ry;
1397 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1398 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1399
1400 // Check if the radii are big enough to draw the arc, scale radii if not.
1401 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1402 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1403 if (radiiScale > 1) {
1404 radiiScale = SkScalarSqrt(radiiScale);
1405 rx *= radiiScale;
1406 ry *= radiiScale;
1407 }
1408
1409 pointTransform.setScale(1 / rx, 1 / ry);
1410 pointTransform.preRotate(-angle);
1411
1412 SkPoint unitPts[2];
1413 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1414 SkVector delta = unitPts[1] - unitPts[0];
1415
1416 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1417 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1418
1419 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1420 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1421 scaleFactor = -scaleFactor;
1422 }
1423 delta.scale(scaleFactor);
1424 SkPoint centerPoint = unitPts[0] + unitPts[1];
1425 centerPoint *= 0.5f;
1426 centerPoint.offset(-delta.fY, delta.fX);
1427 unitPts[0] -= centerPoint;
1428 unitPts[1] -= centerPoint;
1429 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1430 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1431 SkScalar thetaArc = theta2 - theta1;
1432 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1433 thetaArc += SK_ScalarPI * 2;
1434 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1435 thetaArc -= SK_ScalarPI * 2;
1436 }
1437 pointTransform.setRotate(angle);
1438 pointTransform.preScale(rx, ry);
1439
Cary Clark1acd3c72017-12-08 11:37:01 -05001440#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001441 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001442#else
1443 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1444 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1445#endif
caryclark55d49052016-01-23 05:07:04 -08001446 SkScalar thetaWidth = thetaArc / segments;
1447 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1448 if (!SkScalarIsFinite(t)) {
1449 return;
1450 }
1451 SkScalar startTheta = theta1;
1452 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001453#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1454 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1455 return scalar == SkScalarFloorToScalar(scalar);
1456 };
1457 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1458 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1459 scalar_is_integer(x) && scalar_is_integer(y);
1460#endif
caryclark55d49052016-01-23 05:07:04 -08001461 for (int i = 0; i < segments; ++i) {
1462 SkScalar endTheta = startTheta + thetaWidth;
1463 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1464
1465 unitPts[1].set(cosEndTheta, sinEndTheta);
1466 unitPts[1] += centerPoint;
1467 unitPts[0] = unitPts[1];
1468 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1469 SkPoint mapped[2];
1470 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001471 /*
1472 Computing the arc width introduces rounding errors that cause arcs to start
1473 outside their marks. A round rect may lose convexity as a result. If the input
1474 values are on integers, place the conic on integers as well.
1475 */
1476#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1477 if (expectIntegers) {
1478 SkScalar* mappedScalars = &mapped[0].fX;
1479 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1480 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1481 }
1482 }
1483#endif
caryclark55d49052016-01-23 05:07:04 -08001484 this->conicTo(mapped[0], mapped[1], w);
1485 startTheta = endTheta;
1486 }
1487}
1488
1489void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1490 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1491 SkPoint currentPoint;
1492 this->getLastPt(&currentPoint);
1493 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1494}
1495
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001496void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 if (oval.isEmpty() || 0 == sweepAngle) {
1498 return;
1499 }
1500
1501 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1502
1503 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001504 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1505 // See SkPath::addOval() docs.
1506 SkScalar startOver90 = startAngle / 90.f;
1507 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1508 SkScalar error = startOver90 - startOver90I;
1509 if (SkScalarNearlyEqual(error, 0)) {
1510 // Index 1 is at startAngle == 0.
1511 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1512 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1513 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1514 (unsigned) startIndex);
1515 return;
1516 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 }
bsalomon1978ce22016-05-31 10:42:16 -07001518 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519}
1520
1521/*
1522 Need to handle the case when the angle is sharp, and our computed end-points
1523 for the arc go behind pt1 and/or p2...
1524*/
reedc7789042015-01-29 12:59:11 -08001525void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001526 if (radius == 0) {
1527 this->lineTo(x1, y1);
1528 return;
1529 }
1530
1531 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001532
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 // need to know our prev pt so we can construct tangent vectors
1534 {
1535 SkPoint start;
1536 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001537 // Handle degenerate cases by adding a line to the first point and
1538 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 before.setNormalize(x1 - start.fX, y1 - start.fY);
1540 after.setNormalize(x2 - x1, y2 - y1);
1541 }
reed@google.comabf15c12011-01-18 20:35:51 +00001542
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 SkScalar cosh = SkPoint::DotProduct(before, after);
1544 SkScalar sinh = SkPoint::CrossProduct(before, after);
1545
1546 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001547 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 return;
1549 }
reed@google.comabf15c12011-01-18 20:35:51 +00001550
Mike Reeda99b6ce2017-02-04 11:04:26 -05001551 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552
Mike Reeda99b6ce2017-02-04 11:04:26 -05001553 SkScalar xx = x1 - dist * before.fX;
1554 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001555 after.setLength(dist);
1556 this->lineTo(xx, yy);
1557 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1558 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559}
1560
1561///////////////////////////////////////////////////////////////////////////////
1562
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001563void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 SkMatrix matrix;
1565
1566 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001567 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568}
1569
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001570void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001571 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001573 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 SkPoint pts[4];
1575 Verb verb;
1576
Cary Clark9480d822017-10-19 18:01:13 -04001577 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001578 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 while ((verb = iter.next(pts)) != kDone_Verb) {
1580 switch (verb) {
1581 case kMove_Verb:
1582 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001583 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1584 injectMoveToIfNeeded(); // In case last contour is closed
1585 this->lineTo(pts[0]);
1586 } else {
1587 this->moveTo(pts[0]);
1588 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 break;
1590 case kLine_Verb:
1591 proc(matrix, &pts[1], &pts[1], 1);
1592 this->lineTo(pts[1]);
1593 break;
1594 case kQuad_Verb:
1595 proc(matrix, &pts[1], &pts[1], 2);
1596 this->quadTo(pts[1], pts[2]);
1597 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001598 case kConic_Verb:
1599 proc(matrix, &pts[1], &pts[1], 2);
1600 this->conicTo(pts[1], pts[2], iter.conicWeight());
1601 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602 case kCubic_Verb:
1603 proc(matrix, &pts[1], &pts[1], 3);
1604 this->cubicTo(pts[1], pts[2], pts[3]);
1605 break;
1606 case kClose_Verb:
1607 this->close();
1608 break;
1609 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001610 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001612 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 }
1614}
1615
1616///////////////////////////////////////////////////////////////////////////////
1617
reed@google.com277c3f82013-05-31 15:17:50 +00001618static int pts_in_verb(unsigned verb) {
1619 static const uint8_t gPtsInVerb[] = {
1620 1, // kMove
1621 1, // kLine
1622 2, // kQuad
1623 2, // kConic
1624 3, // kCubic
1625 0, // kClose
1626 0 // kDone
1627 };
1628
1629 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1630 return gPtsInVerb[verb];
1631}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633// ignore the last point of the 1st contour
1634void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001635 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1636 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 return;
1638 }
caryclark51c56782016-11-07 05:09:28 -08001639 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1640 SkASSERT(verbsEnd[0] == kMove_Verb);
1641 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1642 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643
caryclark51c56782016-11-07 05:09:28 -08001644 while (verbs < verbsEnd) {
1645 uint8_t v = *verbs++;
1646 pts -= pts_in_verb(v);
1647 switch (v) {
1648 case kMove_Verb:
1649 // if the path has multiple contours, stop after reversing the last
1650 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001652 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 break;
1654 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001655 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001657 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001658 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001659 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001660 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001661 this->cubicTo(pts[2], pts[1], pts[0]);
1662 break;
1663 case kClose_Verb:
1664 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001665 break;
1666 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001667 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 break;
1669 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 }
1671}
1672
reed@google.com63d73742012-01-10 15:33:12 +00001673void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001674 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001675
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001676 const SkPoint* pts = src.fPathRef->pointsEnd();
1677 // we will iterator through src's verbs backwards
1678 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1679 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001680 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001681
1682 bool needMove = true;
1683 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001684 while (verbs < verbsEnd) {
1685 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001686 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001687
1688 if (needMove) {
1689 --pts;
1690 this->moveTo(pts->fX, pts->fY);
1691 needMove = false;
1692 }
1693 pts -= n;
1694 switch (v) {
1695 case kMove_Verb:
1696 if (needClose) {
1697 this->close();
1698 needClose = false;
1699 }
1700 needMove = true;
1701 pts += 1; // so we see the point in "if (needMove)" above
1702 break;
1703 case kLine_Verb:
1704 this->lineTo(pts[0]);
1705 break;
1706 case kQuad_Verb:
1707 this->quadTo(pts[1], pts[0]);
1708 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001709 case kConic_Verb:
1710 this->conicTo(pts[1], pts[0], *--conicWeights);
1711 break;
reed@google.com63d73742012-01-10 15:33:12 +00001712 case kCubic_Verb:
1713 this->cubicTo(pts[2], pts[1], pts[0]);
1714 break;
1715 case kClose_Verb:
1716 needClose = true;
1717 break;
1718 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001719 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001720 }
1721 }
1722}
1723
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724///////////////////////////////////////////////////////////////////////////////
1725
1726void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1727 SkMatrix matrix;
1728
1729 matrix.setTranslate(dx, dy);
1730 this->transform(matrix, dst);
1731}
1732
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1734 int level = 2) {
1735 if (--level >= 0) {
1736 SkPoint tmp[7];
1737
1738 SkChopCubicAtHalf(pts, tmp);
1739 subdivide_cubic_to(path, &tmp[0], level);
1740 subdivide_cubic_to(path, &tmp[3], level);
1741 } else {
1742 path->cubicTo(pts[1], pts[2], pts[3]);
1743 }
1744}
1745
1746void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1747 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001748 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 dst = (SkPath*)this;
1750 }
1751
tomhudson@google.com8d430182011-06-06 19:11:19 +00001752 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 SkPath tmp;
1754 tmp.fFillType = fFillType;
1755
1756 SkPath::Iter iter(*this, false);
1757 SkPoint pts[4];
1758 SkPath::Verb verb;
1759
reed@google.com4a3b7142012-05-16 17:16:46 +00001760 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 switch (verb) {
1762 case kMove_Verb:
1763 tmp.moveTo(pts[0]);
1764 break;
1765 case kLine_Verb:
1766 tmp.lineTo(pts[1]);
1767 break;
1768 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001769 // promote the quad to a conic
1770 tmp.conicTo(pts[1], pts[2],
1771 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001773 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001774 tmp.conicTo(pts[1], pts[2],
1775 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001776 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 case kCubic_Verb:
1778 subdivide_cubic_to(&tmp, pts);
1779 break;
1780 case kClose_Verb:
1781 tmp.close();
1782 break;
1783 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001784 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 break;
1786 }
1787 }
1788
1789 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001790 SkPathRef::Editor ed(&dst->fPathRef);
1791 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001792 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001794 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1795
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001798 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001799 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001801
reed026beb52015-06-10 14:23:15 -07001802 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1803 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001804 } else {
1805 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001806 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1807 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001808 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001809 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1810 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001811 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001812 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001813 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001814 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001815 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001816 }
1817 }
1818
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 SkDEBUGCODE(dst->validate();)
1820 }
1821}
1822
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823///////////////////////////////////////////////////////////////////////////////
1824///////////////////////////////////////////////////////////////////////////////
1825
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826SkPath::Iter::Iter() {
1827#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001828 fPts = nullptr;
1829 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001832 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833#endif
1834 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001835 fVerbs = nullptr;
1836 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 fNeedClose = false;
1838}
1839
1840SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1841 this->setPath(path, forceClose);
1842}
1843
1844void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 fPts = path.fPathRef->points();
1846 fVerbs = path.fPathRef->verbs();
1847 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001848 fConicWeights = path.fPathRef->conicWeights();
1849 if (fConicWeights) {
1850 fConicWeights -= 1; // begin one behind
1851 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001853 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 fForceClose = SkToU8(forceClose);
1855 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001856 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857}
1858
1859bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001860 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 return false;
1862 }
1863 if (fForceClose) {
1864 return true;
1865 }
1866
1867 const uint8_t* verbs = fVerbs;
1868 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001869
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 if (kMove_Verb == *(verbs - 1)) {
1871 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872 }
1873
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 while (verbs > stop) {
1875 // verbs points one beyond the current verb, decrement first.
1876 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 if (kMove_Verb == v) {
1878 break;
1879 }
1880 if (kClose_Verb == v) {
1881 return true;
1882 }
1883 }
1884 return false;
1885}
1886
1887SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001888 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001890 // A special case: if both points are NaN, SkPoint::operation== returns
1891 // false, but the iterator expects that they are treated as the same.
1892 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001893 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1894 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001895 return kClose_Verb;
1896 }
1897
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898 pts[0] = fLastPt;
1899 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 fLastPt = fMoveTo;
1901 fCloseLine = true;
1902 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001903 } else {
1904 pts[0] = fMoveTo;
1905 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907}
1908
reed@google.com9e25dbf2012-05-15 17:05:38 +00001909const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 if (fSegmentState == kAfterMove_SegmentState) {
1911 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001915
1916 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1917 // Set the first return pt to the last pt of the previous primitive.
1918 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919}
1920
caryclarke8c56662015-07-14 11:19:26 -07001921void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 // We need to step over anything that will not move the current draw point
1923 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001924 const uint8_t* lastMoveVerb = nullptr;
1925 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001926 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 SkPoint lastPt = fLastPt;
1928 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001929 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 switch (verb) {
1931 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 // Keep a record of this most recent move
1933 lastMoveVerb = fVerbs;
1934 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001935 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001936 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 fPts++;
1939 break;
1940
1941 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001942 // A close when we are in a segment is always valid except when it
1943 // follows a move which follows a segment.
1944 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 return;
1946 }
1947 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001948 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001949 break;
1950
1951 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001952 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001953 if (lastMoveVerb) {
1954 fVerbs = lastMoveVerb;
1955 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001956 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 return;
1958 }
1959 return;
1960 }
1961 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001962 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 fPts++;
1964 break;
1965
reed@google.com277c3f82013-05-31 15:17:50 +00001966 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001968 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 if (lastMoveVerb) {
1970 fVerbs = lastMoveVerb;
1971 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001972 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 return;
1974 }
1975 return;
1976 }
1977 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001978 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001980 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001981 break;
1982
1983 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001984 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 if (lastMoveVerb) {
1986 fVerbs = lastMoveVerb;
1987 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001988 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001989 return;
1990 }
1991 return;
1992 }
1993 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001994 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995 fPts += 3;
1996 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001997
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001999 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002000 }
2001 }
2002}
2003
reed@google.com4a3b7142012-05-16 17:16:46 +00002004SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002005 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002006
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002008 // Close the curve if requested and if there is some curve to close
2009 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002010 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011 return kLine_Verb;
2012 }
2013 fNeedClose = false;
2014 return kClose_Verb;
2015 }
2016 return kDone_Verb;
2017 }
2018
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002019 // fVerbs is one beyond the current verb, decrement first
2020 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002021 const SkPoint* SK_RESTRICT srcPts = fPts;
2022 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023
2024 switch (verb) {
2025 case kMove_Verb:
2026 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002027 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 verb = this->autoClose(pts);
2029 if (verb == kClose_Verb) {
2030 fNeedClose = false;
2031 }
2032 return (Verb)verb;
2033 }
2034 if (fVerbs == fVerbStop) { // might be a trailing moveto
2035 return kDone_Verb;
2036 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002037 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002038 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002040 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002041 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 fNeedClose = fForceClose;
2043 break;
2044 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002045 pts[0] = this->cons_moveTo();
2046 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 fLastPt = srcPts[0];
2048 fCloseLine = false;
2049 srcPts += 1;
2050 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002051 case kConic_Verb:
2052 fConicWeights += 1;
2053 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002055 pts[0] = this->cons_moveTo();
2056 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 fLastPt = srcPts[1];
2058 srcPts += 2;
2059 break;
2060 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002061 pts[0] = this->cons_moveTo();
2062 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 fLastPt = srcPts[2];
2064 srcPts += 3;
2065 break;
2066 case kClose_Verb:
2067 verb = this->autoClose(pts);
2068 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002069 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 } else {
2071 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002072 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002074 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002075 break;
2076 }
2077 fPts = srcPts;
2078 return (Verb)verb;
2079}
2080
2081///////////////////////////////////////////////////////////////////////////////
2082
Ben Wagner4d1955c2017-03-10 13:08:15 -05002083#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002084#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002085#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002086
reed@google.com51bbe752013-01-17 22:07:50 +00002087static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002088 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002089 str->append(label);
2090 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002091
reed@google.com51bbe752013-01-17 22:07:50 +00002092 const SkScalar* values = &pts[0].fX;
2093 count *= 2;
2094
2095 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002096 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002097 if (i < count - 1) {
2098 str->append(", ");
2099 }
2100 }
Mike Reed405b9d22018-01-09 15:11:08 -05002101 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002102 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002103 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002104 }
caryclark08fa28c2014-10-23 13:08:56 -07002105 str->append(");");
reede05fed02014-12-15 07:59:53 -08002106 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002107 str->append(" // ");
2108 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002109 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002110 if (i < count - 1) {
2111 str->append(", ");
2112 }
2113 }
2114 if (conicWeight >= 0) {
2115 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002116 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002117 }
2118 }
2119 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002120}
2121
caryclarke9562592014-09-15 09:26:09 -07002122void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002123 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 Iter iter(*this, forceClose);
2125 SkPoint pts[4];
2126 Verb verb;
2127
reed@google.com51bbe752013-01-17 22:07:50 +00002128 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002129 char const * const gFillTypeStrs[] = {
2130 "Winding",
2131 "EvenOdd",
2132 "InverseWinding",
2133 "InverseEvenOdd",
2134 };
2135 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2136 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002137 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 switch (verb) {
2139 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
2142 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
2145 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002148 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002149 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002150 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002152 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002155 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
2157 default:
2158 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2159 verb = kDone_Verb; // stop the loop
2160 break;
2161 }
caryclark1049f122015-04-20 08:31:59 -07002162 if (!wStream && builder.size()) {
2163 SkDebugf("%s", builder.c_str());
2164 builder.reset();
2165 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 }
caryclark66a5d8b2014-06-24 08:30:15 -07002167 if (wStream) {
2168 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002169 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170}
2171
reed@android.come522ca52009-11-23 20:10:41 +00002172void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002173 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002174}
2175
2176void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002177 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002178}
2179
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002180
Cary Clark0461e9f2017-08-25 15:13:38 -04002181bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002182 if ((fFillType & ~3) != 0) {
2183 return false;
2184 }
reed@google.comabf15c12011-01-18 20:35:51 +00002185
djsollen@google.com077348c2012-10-22 20:23:32 +00002186#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002187 if (!fBoundsIsDirty) {
2188 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002189
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002190 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002191 if (SkToBool(fIsFinite) != isFinite) {
2192 return false;
2193 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002194
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002195 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002196 // if we're empty, fBounds may be empty but translated, so we can't
2197 // necessarily compare to bounds directly
2198 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2199 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002200 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2201 return false;
2202 }
reed@android.come522ca52009-11-23 20:10:41 +00002203 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002204 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002205 if (!fBounds.isEmpty()) {
2206 return false;
2207 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002209 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002210 if (!fBounds.contains(bounds)) {
2211 return false;
2212 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002213 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002214 }
reed@android.come522ca52009-11-23 20:10:41 +00002215 }
2216 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002217#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002218 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002219}
reed@android.come522ca52009-11-23 20:10:41 +00002220
reed@google.com04863fa2011-05-15 04:08:24 +00002221///////////////////////////////////////////////////////////////////////////////
2222
reed@google.com0b7b9822011-05-16 12:29:27 +00002223static int sign(SkScalar x) { return x < 0; }
2224#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002225
robertphillipsc506e302014-09-16 09:43:31 -07002226enum DirChange {
2227 kLeft_DirChange,
2228 kRight_DirChange,
2229 kStraight_DirChange,
2230 kBackwards_DirChange,
2231
2232 kInvalid_DirChange
2233};
2234
2235
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002236static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 // The error epsilon was empirically derived; worse case round rects
2238 // with a mid point outset by 2x float epsilon in tests had an error
2239 // of 12.
2240 const int epsilon = 16;
2241 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2242 return false;
2243 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002244 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002245 int aBits = SkFloatAs2sCompliment(compA);
2246 int bBits = SkFloatAs2sCompliment(compB);
2247 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002248}
2249
caryclarkb4216502015-03-02 13:02:34 -08002250static bool approximately_zero_when_compared_to(double x, double y) {
2251 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002252}
2253
caryclarkb4216502015-03-02 13:02:34 -08002254
reed@google.com04863fa2011-05-15 04:08:24 +00002255// only valid for a single contour
2256struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002257 Convexicator()
2258 : fPtCount(0)
2259 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002260 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002261 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002262 , fIsCurve(false)
2263 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002264 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002265 // warnings
djsollen2f124632016-04-29 13:53:05 -07002266 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002267 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002268 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002269 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002270 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002271
2272 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002273 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002274 }
2275
2276 SkPath::Convexity getConvexity() const { return fConvexity; }
2277
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002278 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002279 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280
reed@google.com04863fa2011-05-15 04:08:24 +00002281 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002282 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002283 return;
2284 }
2285
2286 if (0 == fPtCount) {
2287 fCurrPt = pt;
2288 ++fPtCount;
2289 } else {
2290 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002291 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002292 if (!SkScalarIsFinite(lengthSqd)) {
2293 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002294 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002295 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002296 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002297 fCurrPt = pt;
2298 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002299 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002300 } else {
2301 SkASSERT(fPtCount > 2);
2302 this->addVec(vec);
2303 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002304
reed@google.com85b6e392011-05-15 20:25:17 +00002305 int sx = sign(vec.fX);
2306 int sy = sign(vec.fY);
2307 fDx += (sx != fSx);
2308 fDy += (sy != fSy);
2309 fSx = sx;
2310 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002311
reed@google.com85b6e392011-05-15 20:25:17 +00002312 if (fDx > 3 || fDy > 3) {
2313 fConvexity = SkPath::kConcave_Convexity;
2314 }
reed@google.com04863fa2011-05-15 04:08:24 +00002315 }
2316 }
2317 }
2318
2319 void close() {
2320 if (fPtCount > 2) {
2321 this->addVec(fFirstVec);
2322 }
2323 }
2324
caryclarkb4216502015-03-02 13:02:34 -08002325 DirChange directionChange(const SkVector& curVec) {
2326 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2327
2328 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2329 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2330 largest = SkTMax(largest, -smallest);
2331
2332 if (!almost_equal(largest, largest + cross)) {
2333 int sign = SkScalarSignAsInt(cross);
2334 if (sign) {
2335 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2336 }
2337 }
2338
2339 if (cross) {
2340 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2341 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2342 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2343 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2344 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2345 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2346 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2347 if (sign) {
2348 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2349 }
2350 }
2351 }
2352
Cary Clarkdf429f32017-11-08 11:44:31 -05002353 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2354 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2355 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2356 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002357 fLastVec.dot(curVec) < 0.0f) {
2358 return kBackwards_DirChange;
2359 }
2360
2361 return kStraight_DirChange;
2362 }
2363
Cary Clarkc8323aa2017-08-25 08:04:43 -04002364 bool hasBackwards() const {
2365 return fBackwards;
2366 }
caryclarkb4216502015-03-02 13:02:34 -08002367
caryclarkd3d1a982014-12-08 04:57:38 -08002368 bool isFinite() const {
2369 return fIsFinite;
2370 }
2371
caryclark5ccef572015-03-02 10:07:56 -08002372 void setCurve(bool isCurve) {
2373 fIsCurve = isCurve;
2374 }
2375
reed@google.com04863fa2011-05-15 04:08:24 +00002376private:
2377 void addVec(const SkVector& vec) {
2378 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002379 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002380 switch (dir) {
2381 case kLeft_DirChange: // fall through
2382 case kRight_DirChange:
2383 if (kInvalid_DirChange == fExpectedDir) {
2384 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002385 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2386 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002387 } else if (dir != fExpectedDir) {
2388 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002389 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002390 }
2391 fLastVec = vec;
2392 break;
2393 case kStraight_DirChange:
2394 break;
2395 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002396 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002397 // If any of the subsequent dir is non-backward, it'll be concave.
2398 // Otherwise, it's still convex.
2399 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002400 }
robertphillipsc506e302014-09-16 09:43:31 -07002401 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002402 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002403 break;
2404 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002405 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002406 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 }
2408 }
2409
caryclarkb4216502015-03-02 13:02:34 -08002410 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002411 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002413 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2414 // value with the current vec is deemed to be of a significant value.
2415 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002416 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002417 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002419 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002420 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002421 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002422 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002423 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002424};
2425
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002426SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002427 // Sometimes we think we need to calculate convexity but another thread already did.
2428 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2429 return c;
2430 }
2431
reed@google.com04863fa2011-05-15 04:08:24 +00002432 SkPoint pts[4];
2433 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002434 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002435
2436 int contourCount = 0;
2437 int count;
2438 Convexicator state;
2439
caryclarkd3d1a982014-12-08 04:57:38 -08002440 if (!isFinite()) {
2441 return kUnknown_Convexity;
2442 }
Brian Osman205a1262017-09-18 13:13:48 +00002443 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002444 switch (verb) {
2445 case kMove_Verb:
2446 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002447 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002448 return kConcave_Convexity;
2449 }
2450 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002451 // fall through
2452 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002453 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002454 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002455 break;
caryclark5ccef572015-03-02 10:07:56 -08002456 case kQuad_Verb:
2457 // fall through
2458 case kConic_Verb:
2459 // fall through
2460 case kCubic_Verb:
2461 count = 2 + (kCubic_Verb == verb);
2462 // As an additional enhancement, this could set curve true only
2463 // if the curve is nonlinear
2464 state.setCurve(true);
2465 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002466 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002467 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002468 state.close();
2469 count = 0;
2470 break;
2471 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002472 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002473 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002474 return kConcave_Convexity;
2475 }
2476
2477 for (int i = 1; i <= count; i++) {
2478 state.addPt(pts[i]);
2479 }
2480 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002481 if (!state.isFinite()) {
2482 return kUnknown_Convexity;
2483 }
reed@google.com04863fa2011-05-15 04:08:24 +00002484 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002485 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002486 return kConcave_Convexity;
2487 }
2488 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002489 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002490 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002491 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2492 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2493 fConvexity = Convexity::kConcave_Convexity;
2494 } else {
2495 fFirstDirection = state.getFirstDirection();
2496 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002497 }
2498 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002499}
reed@google.com69a99432012-01-10 18:00:10 +00002500
2501///////////////////////////////////////////////////////////////////////////////
2502
2503class ContourIter {
2504public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002505 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002506
2507 bool done() const { return fDone; }
2508 // if !done() then these may be called
2509 int count() const { return fCurrPtCount; }
2510 const SkPoint* pts() const { return fCurrPt; }
2511 void next();
2512
2513private:
2514 int fCurrPtCount;
2515 const SkPoint* fCurrPt;
2516 const uint8_t* fCurrVerb;
2517 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002518 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002519 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002520 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002521};
2522
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002523ContourIter::ContourIter(const SkPathRef& pathRef) {
2524 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002525 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002526 fCurrPt = pathRef.points();
2527 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002528 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002529 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002530 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002531 this->next();
2532}
2533
2534void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002535 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002536 fDone = true;
2537 }
2538 if (fDone) {
2539 return;
2540 }
2541
2542 // skip pts of prev contour
2543 fCurrPt += fCurrPtCount;
2544
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002545 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002546 int ptCount = 1; // moveTo
2547 const uint8_t* verbs = fCurrVerb;
2548
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002549 for (--verbs; verbs > fStopVerbs; --verbs) {
2550 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002551 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002552 goto CONTOUR_END;
2553 case SkPath::kLine_Verb:
2554 ptCount += 1;
2555 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002556 case SkPath::kConic_Verb:
2557 fCurrConicWeight += 1;
2558 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002559 case SkPath::kQuad_Verb:
2560 ptCount += 2;
2561 break;
2562 case SkPath::kCubic_Verb:
2563 ptCount += 3;
2564 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002565 case SkPath::kClose_Verb:
2566 break;
2567 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002568 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002569 break;
2570 }
2571 }
2572CONTOUR_END:
2573 fCurrPtCount = ptCount;
2574 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002575 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002576}
2577
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002578// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002579static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002580 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2581 // We may get 0 when the above subtracts underflow. We expect this to be
2582 // very rare and lazily promote to double.
2583 if (0 == cross) {
2584 double p0x = SkScalarToDouble(p0.fX);
2585 double p0y = SkScalarToDouble(p0.fY);
2586
2587 double p1x = SkScalarToDouble(p1.fX);
2588 double p1y = SkScalarToDouble(p1.fY);
2589
2590 double p2x = SkScalarToDouble(p2.fX);
2591 double p2y = SkScalarToDouble(p2.fY);
2592
2593 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2594 (p1y - p0y) * (p2x - p0x));
2595
2596 }
2597 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002598}
2599
reed@google.comc1ea60a2012-01-31 15:15:36 +00002600// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002601static int find_max_y(const SkPoint pts[], int count) {
2602 SkASSERT(count > 0);
2603 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002604 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002605 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002606 SkScalar y = pts[i].fY;
2607 if (y > max) {
2608 max = y;
2609 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002610 }
2611 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002612 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002613}
2614
reed@google.comcabaf1d2012-01-11 21:03:05 +00002615static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2616 int i = index;
2617 for (;;) {
2618 i = (i + inc) % n;
2619 if (i == index) { // we wrapped around, so abort
2620 break;
2621 }
2622 if (pts[index] != pts[i]) { // found a different point, success!
2623 break;
2624 }
2625 }
2626 return i;
2627}
2628
reed@google.comc1ea60a2012-01-31 15:15:36 +00002629/**
2630 * Starting at index, and moving forward (incrementing), find the xmin and
2631 * xmax of the contiguous points that have the same Y.
2632 */
2633static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2634 int* maxIndexPtr) {
2635 const SkScalar y = pts[index].fY;
2636 SkScalar min = pts[index].fX;
2637 SkScalar max = min;
2638 int minIndex = index;
2639 int maxIndex = index;
2640 for (int i = index + 1; i < n; ++i) {
2641 if (pts[i].fY != y) {
2642 break;
2643 }
2644 SkScalar x = pts[i].fX;
2645 if (x < min) {
2646 min = x;
2647 minIndex = i;
2648 } else if (x > max) {
2649 max = x;
2650 maxIndex = i;
2651 }
2652 }
2653 *maxIndexPtr = maxIndex;
2654 return minIndex;
2655}
2656
reed026beb52015-06-10 14:23:15 -07002657static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2658 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002659}
2660
reed@google.comac8543f2012-01-30 20:51:25 +00002661/*
2662 * We loop through all contours, and keep the computed cross-product of the
2663 * contour that contained the global y-max. If we just look at the first
2664 * contour, we may find one that is wound the opposite way (correctly) since
2665 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2666 * that is outer most (or at least has the global y-max) before we can consider
2667 * its cross product.
2668 */
reed026beb52015-06-10 14:23:15 -07002669bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002670 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2671 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002672 return true;
2673 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002674
2675 // don't want to pay the cost for computing this if it
2676 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002677 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2678 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002679 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002680 return false;
2681 }
reed@google.com69a99432012-01-10 18:00:10 +00002682
reed026beb52015-06-10 14:23:15 -07002683 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002684
reed@google.comac8543f2012-01-30 20:51:25 +00002685 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002686 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002687 SkScalar ymaxCross = 0;
2688
reed@google.com69a99432012-01-10 18:00:10 +00002689 for (; !iter.done(); iter.next()) {
2690 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002691 if (n < 3) {
2692 continue;
2693 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002694
reed@google.comcabaf1d2012-01-11 21:03:05 +00002695 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002696 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002697 int index = find_max_y(pts, n);
2698 if (pts[index].fY < ymax) {
2699 continue;
2700 }
2701
2702 // If there is more than 1 distinct point at the y-max, we take the
2703 // x-min and x-max of them and just subtract to compute the dir.
2704 if (pts[(index + 1) % n].fY == pts[index].fY) {
2705 int maxIndex;
2706 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2707 if (minIndex == maxIndex) {
2708 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002709 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002710 SkASSERT(pts[minIndex].fY == pts[index].fY);
2711 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2712 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2713 // we just subtract the indices, and let that auto-convert to
2714 // SkScalar, since we just want - or + to signal the direction.
2715 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002716 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002717 TRY_CROSSPROD:
2718 // Find a next and prev index to use for the cross-product test,
2719 // but we try to find pts that form non-zero vectors from pts[index]
2720 //
2721 // Its possible that we can't find two non-degenerate vectors, so
2722 // we have to guard our search (e.g. all the pts could be in the
2723 // same place).
2724
2725 // we pass n - 1 instead of -1 so we don't foul up % operator by
2726 // passing it a negative LH argument.
2727 int prev = find_diff_pt(pts, index, n, n - 1);
2728 if (prev == index) {
2729 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002730 continue;
2731 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002732 int next = find_diff_pt(pts, index, n, 1);
2733 SkASSERT(next != index);
2734 cross = cross_prod(pts[prev], pts[index], pts[next]);
2735 // if we get a zero and the points are horizontal, then we look at the spread in
2736 // x-direction. We really should continue to walk away from the degeneracy until
2737 // there is a divergence.
2738 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2739 // construct the subtract so we get the correct Direction below
2740 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002741 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002742 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002743
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002744 if (cross) {
2745 // record our best guess so far
2746 ymax = pts[index].fY;
2747 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002748 }
2749 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002750 if (ymaxCross) {
2751 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002752 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002753 return true;
2754 } else {
2755 return false;
2756 }
reed@google.comac8543f2012-01-30 20:51:25 +00002757}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758
2759///////////////////////////////////////////////////////////////////////////////
2760
caryclark9aacd902015-12-14 08:38:09 -08002761static bool between(SkScalar a, SkScalar b, SkScalar c) {
2762 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2763 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2764 return (a - b) * (c - b) <= 0;
2765}
2766
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002767static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2768 SkScalar t) {
2769 SkScalar A = c3 + 3*(c1 - c2) - c0;
2770 SkScalar B = 3*(c2 - c1 - c1 + c0);
2771 SkScalar C = 3*(c1 - c0);
2772 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002773 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774}
2775
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776template <size_t N> static void find_minmax(const SkPoint pts[],
2777 SkScalar* minPtr, SkScalar* maxPtr) {
2778 SkScalar min, max;
2779 min = max = pts[0].fX;
2780 for (size_t i = 1; i < N; ++i) {
2781 min = SkMinScalar(min, pts[i].fX);
2782 max = SkMaxScalar(max, pts[i].fX);
2783 }
2784 *minPtr = min;
2785 *maxPtr = max;
2786}
2787
caryclark9cb5d752015-12-18 04:35:24 -08002788static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2789 if (start.fY == end.fY) {
2790 return between(start.fX, x, end.fX) && x != end.fX;
2791 } else {
2792 return x == start.fX && y == start.fY;
2793 }
2794}
2795
caryclark9aacd902015-12-14 08:38:09 -08002796static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002797 SkScalar y0 = pts[0].fY;
2798 SkScalar y3 = pts[3].fY;
2799
2800 int dir = 1;
2801 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002802 using std::swap;
2803 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002804 dir = -1;
2805 }
2806 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 return 0;
2808 }
caryclark9cb5d752015-12-18 04:35:24 -08002809 if (checkOnCurve(x, y, pts[0], pts[3])) {
2810 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002811 return 0;
2812 }
caryclark9cb5d752015-12-18 04:35:24 -08002813 if (y == y3) {
2814 return 0;
2815 }
2816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // quickreject or quickaccept
2818 SkScalar min, max;
2819 find_minmax<4>(pts, &min, &max);
2820 if (x < min) {
2821 return 0;
2822 }
2823 if (x > max) {
2824 return dir;
2825 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002828 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002829 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002830 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002831 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002832 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002833 if (SkScalarNearlyEqual(xt, x)) {
2834 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2835 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002836 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002837 }
2838 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 return xt < x ? dir : 0;
2840}
2841
caryclark9aacd902015-12-14 08:38:09 -08002842static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 SkPoint dst[10];
2844 int n = SkChopCubicAtYExtrema(pts, dst);
2845 int w = 0;
2846 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002847 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 }
2849 return w;
2850}
2851
caryclark9aacd902015-12-14 08:38:09 -08002852static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2853 SkASSERT(src);
2854 SkASSERT(t >= 0 && t <= 1);
2855 SkScalar src2w = src[2] * w;
2856 SkScalar C = src[0];
2857 SkScalar A = src[4] - 2 * src2w + C;
2858 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002859 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002860}
2861
2862
2863static double conic_eval_denominator(SkScalar w, SkScalar t) {
2864 SkScalar B = 2 * (w - 1);
2865 SkScalar C = 1;
2866 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002867 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002868}
2869
2870static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2871 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 SkScalar y0 = pts[0].fY;
2873 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 int dir = 1;
2876 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002877 using std::swap;
2878 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 dir = -1;
2880 }
caryclark9aacd902015-12-14 08:38:09 -08002881 if (y < y0 || y > y2) {
2882 return 0;
2883 }
caryclark9cb5d752015-12-18 04:35:24 -08002884 if (checkOnCurve(x, y, pts[0], pts[2])) {
2885 *onCurveCount += 1;
2886 return 0;
2887 }
caryclark9aacd902015-12-14 08:38:09 -08002888 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 return 0;
2890 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002891
caryclark9aacd902015-12-14 08:38:09 -08002892 SkScalar roots[2];
2893 SkScalar A = pts[2].fY;
2894 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2895 SkScalar C = pts[0].fY;
2896 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2897 B -= C; // B = b*w - w * yCept + yCept - a
2898 C -= y;
2899 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2900 SkASSERT(n <= 1);
2901 SkScalar xt;
2902 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002903 // zero roots are returned only when y0 == y
2904 // Need [0] if dir == 1
2905 // and [2] if dir == -1
2906 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002907 } else {
2908 SkScalar t = roots[0];
2909 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2910 }
2911 if (SkScalarNearlyEqual(xt, x)) {
2912 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2913 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002914 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002915 }
2916 }
2917 return xt < x ? dir : 0;
2918}
2919
2920static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2921 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2922 if (y0 == y1) {
2923 return true;
2924 }
2925 if (y0 < y1) {
2926 return y1 <= y2;
2927 } else {
2928 return y1 >= y2;
2929 }
2930}
2931
2932static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2933 int* onCurveCount) {
2934 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002935 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002936 // If the data points are very large, the conic may not be monotonic but may also
2937 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002938 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2939 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2940 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002941 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2942 }
2943 return w;
2944}
2945
2946static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2947 SkScalar y0 = pts[0].fY;
2948 SkScalar y2 = pts[2].fY;
2949
2950 int dir = 1;
2951 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002952 using std::swap;
2953 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08002954 dir = -1;
2955 }
2956 if (y < y0 || y > y2) {
2957 return 0;
2958 }
caryclark9cb5d752015-12-18 04:35:24 -08002959 if (checkOnCurve(x, y, pts[0], pts[2])) {
2960 *onCurveCount += 1;
2961 return 0;
2962 }
caryclark9aacd902015-12-14 08:38:09 -08002963 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002964 return 0;
2965 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002966 // bounds check on X (not required. is it faster?)
2967#if 0
2968 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2969 return 0;
2970 }
2971#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002972
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002973 SkScalar roots[2];
2974 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2975 2 * (pts[1].fY - pts[0].fY),
2976 pts[0].fY - y,
2977 roots);
2978 SkASSERT(n <= 1);
2979 SkScalar xt;
2980 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002981 // zero roots are returned only when y0 == y
2982 // Need [0] if dir == 1
2983 // and [2] if dir == -1
2984 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 } else {
2986 SkScalar t = roots[0];
2987 SkScalar C = pts[0].fX;
2988 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2989 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002990 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 }
caryclark9aacd902015-12-14 08:38:09 -08002992 if (SkScalarNearlyEqual(xt, x)) {
2993 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2994 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002995 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002996 }
2997 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 return xt < x ? dir : 0;
2999}
3000
caryclark9aacd902015-12-14 08:38:09 -08003001static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 SkPoint dst[5];
3003 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3006 n = SkChopQuadAtYExtrema(pts, dst);
3007 pts = dst;
3008 }
caryclark9aacd902015-12-14 08:38:09 -08003009 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003011 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 }
3013 return w;
3014}
3015
caryclark9aacd902015-12-14 08:38:09 -08003016static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003017 SkScalar x0 = pts[0].fX;
3018 SkScalar y0 = pts[0].fY;
3019 SkScalar x1 = pts[1].fX;
3020 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003021
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003023
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003024 int dir = 1;
3025 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003026 using std::swap;
3027 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 dir = -1;
3029 }
caryclark9aacd902015-12-14 08:38:09 -08003030 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 return 0;
3032 }
caryclark9cb5d752015-12-18 04:35:24 -08003033 if (checkOnCurve(x, y, pts[0], pts[1])) {
3034 *onCurveCount += 1;
3035 return 0;
3036 }
3037 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003038 return 0;
3039 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003040 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003041
caryclark9aacd902015-12-14 08:38:09 -08003042 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003043 // zero cross means the point is on the line, and since the case where
3044 // y of the query point is at the end point is handled above, we can be
3045 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003046 if (x != x1 || y != pts[1].fY) {
3047 *onCurveCount += 1;
3048 }
caryclark9aacd902015-12-14 08:38:09 -08003049 dir = 0;
3050 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003051 dir = 0;
3052 }
3053 return dir;
3054}
3055
caryclark9aacd902015-12-14 08:38:09 -08003056static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3057 SkTDArray<SkVector>* tangents) {
3058 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3059 && !between(pts[2].fY, y, pts[3].fY)) {
3060 return;
3061 }
3062 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3063 && !between(pts[2].fX, x, pts[3].fX)) {
3064 return;
3065 }
3066 SkPoint dst[10];
3067 int n = SkChopCubicAtYExtrema(pts, dst);
3068 for (int i = 0; i <= n; ++i) {
3069 SkPoint* c = &dst[i * 3];
3070 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003071 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003072 continue;
mbarbella276e6332016-05-31 14:44:01 -07003073 }
caryclark9aacd902015-12-14 08:38:09 -08003074 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3075 if (!SkScalarNearlyEqual(x, xt)) {
3076 continue;
3077 }
3078 SkVector tangent;
3079 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003080 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003081 }
3082}
3083
3084static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3085 SkTDArray<SkVector>* tangents) {
3086 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3087 return;
3088 }
3089 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3090 return;
3091 }
3092 SkScalar roots[2];
3093 SkScalar A = pts[2].fY;
3094 SkScalar B = pts[1].fY * w - y * w + y;
3095 SkScalar C = pts[0].fY;
3096 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3097 B -= C; // B = b*w - w * yCept + yCept - a
3098 C -= y;
3099 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3100 for (int index = 0; index < n; ++index) {
3101 SkScalar t = roots[index];
3102 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3103 if (!SkScalarNearlyEqual(x, xt)) {
3104 continue;
3105 }
3106 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003107 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003108 }
3109}
3110
3111static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3112 SkTDArray<SkVector>* tangents) {
3113 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3114 return;
3115 }
3116 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3117 return;
3118 }
3119 SkScalar roots[2];
3120 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3121 2 * (pts[1].fY - pts[0].fY),
3122 pts[0].fY - y,
3123 roots);
3124 for (int index = 0; index < n; ++index) {
3125 SkScalar t = roots[index];
3126 SkScalar C = pts[0].fX;
3127 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3128 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003129 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003130 if (!SkScalarNearlyEqual(x, xt)) {
3131 continue;
3132 }
Mike Reed5edcd312018-08-08 11:23:41 -04003133 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003134 }
3135}
3136
3137static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3138 SkTDArray<SkVector>* tangents) {
3139 SkScalar y0 = pts[0].fY;
3140 SkScalar y1 = pts[1].fY;
3141 if (!between(y0, y, y1)) {
3142 return;
3143 }
3144 SkScalar x0 = pts[0].fX;
3145 SkScalar x1 = pts[1].fX;
3146 if (!between(x0, x, x1)) {
3147 return;
3148 }
3149 SkScalar dx = x1 - x0;
3150 SkScalar dy = y1 - y0;
3151 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3152 return;
3153 }
3154 SkVector v;
3155 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003156 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003157}
3158
reed@google.com4db592c2013-10-30 17:39:43 +00003159static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3160 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3161}
3162
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163bool SkPath::contains(SkScalar x, SkScalar y) const {
3164 bool isInverse = this->isInverseFillType();
3165 if (this->isEmpty()) {
3166 return isInverse;
3167 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003168
reed@google.com4db592c2013-10-30 17:39:43 +00003169 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 return isInverse;
3171 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003172
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003173 SkPath::Iter iter(*this, true);
3174 bool done = false;
3175 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003176 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177 do {
3178 SkPoint pts[4];
3179 switch (iter.next(pts, false)) {
3180 case SkPath::kMove_Verb:
3181 case SkPath::kClose_Verb:
3182 break;
3183 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003184 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003185 break;
3186 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003189 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003190 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003191 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003192 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003193 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 break;
3195 case SkPath::kDone_Verb:
3196 done = true;
3197 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003198 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003199 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003200 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3201 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3202 if (evenOddFill) {
3203 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003204 }
caryclark9aacd902015-12-14 08:38:09 -08003205 if (w) {
3206 return !isInverse;
3207 }
3208 if (onCurveCount <= 1) {
3209 return SkToBool(onCurveCount) ^ isInverse;
3210 }
3211 if ((onCurveCount & 1) || evenOddFill) {
3212 return SkToBool(onCurveCount & 1) ^ isInverse;
3213 }
halcanary9d524f22016-03-29 09:03:52 -07003214 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003215 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3216 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003217 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003218 SkTDArray<SkVector> tangents;
3219 do {
3220 SkPoint pts[4];
3221 int oldCount = tangents.count();
3222 switch (iter.next(pts, false)) {
3223 case SkPath::kMove_Verb:
3224 case SkPath::kClose_Verb:
3225 break;
3226 case SkPath::kLine_Verb:
3227 tangent_line(pts, x, y, &tangents);
3228 break;
3229 case SkPath::kQuad_Verb:
3230 tangent_quad(pts, x, y, &tangents);
3231 break;
3232 case SkPath::kConic_Verb:
3233 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3234 break;
3235 case SkPath::kCubic_Verb:
3236 tangent_cubic(pts, x, y, &tangents);
3237 break;
3238 case SkPath::kDone_Verb:
3239 done = true;
3240 break;
3241 }
3242 if (tangents.count() > oldCount) {
3243 int last = tangents.count() - 1;
3244 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003245 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003246 tangents.remove(last);
3247 } else {
3248 for (int index = 0; index < last; ++index) {
3249 const SkVector& test = tangents[index];
3250 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003251 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3252 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003253 tangents.remove(last);
3254 tangents.removeShuffle(index);
3255 break;
3256 }
3257 }
3258 }
3259 }
3260 } while (!done);
3261 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003262}
fmalitaaa0df4e2015-12-01 09:13:23 -08003263
3264int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3265 SkScalar w, SkPoint pts[], int pow2) {
3266 const SkConic conic(p0, p1, p2, w);
3267 return conic.chopIntoQuadsPOW2(pts, pow2);
3268}
bsalomonedc743a2016-06-01 09:42:31 -07003269
3270bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3271 unsigned* start) {
3272 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3273 return false;
3274 }
3275 SkPath::RawIter iter(path);
3276 SkPoint verbPts[4];
3277 SkPath::Verb v;
3278 SkPoint rectPts[5];
3279 int rectPtCnt = 0;
3280 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3281 switch (v) {
3282 case SkPath::kMove_Verb:
3283 if (0 != rectPtCnt) {
3284 return false;
3285 }
3286 rectPts[0] = verbPts[0];
3287 ++rectPtCnt;
3288 break;
3289 case SkPath::kLine_Verb:
3290 if (5 == rectPtCnt) {
3291 return false;
3292 }
3293 rectPts[rectPtCnt] = verbPts[1];
3294 ++rectPtCnt;
3295 break;
3296 case SkPath::kClose_Verb:
3297 if (4 == rectPtCnt) {
3298 rectPts[4] = rectPts[0];
3299 rectPtCnt = 5;
3300 }
3301 break;
3302 default:
3303 return false;
3304 }
3305 }
3306 if (rectPtCnt < 5) {
3307 return false;
3308 }
3309 if (rectPts[0] != rectPts[4]) {
3310 return false;
3311 }
bsalomon057ae8a2016-07-24 05:37:26 -07003312 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3313 // and pts 1 and 2 the opposite vertical or horizontal edge).
3314 bool vec03IsVertical;
3315 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3316 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3317 // Make sure it has non-zero width and height
3318 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003319 return false;
3320 }
bsalomon057ae8a2016-07-24 05:37:26 -07003321 vec03IsVertical = true;
3322 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3323 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3324 // Make sure it has non-zero width and height
3325 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3326 return false;
3327 }
3328 vec03IsVertical = false;
3329 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003330 return false;
3331 }
bsalomon057ae8a2016-07-24 05:37:26 -07003332 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3333 // set if it is on the bottom edge.
3334 unsigned sortFlags =
3335 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3336 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3337 switch (sortFlags) {
3338 case 0b00:
3339 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3340 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3341 *start = 0;
3342 break;
3343 case 0b01:
3344 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3345 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3346 *start = 1;
3347 break;
3348 case 0b10:
3349 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3350 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3351 *start = 3;
3352 break;
3353 case 0b11:
3354 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3355 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3356 *start = 2;
3357 break;
bsalomonedc743a2016-06-01 09:42:31 -07003358 }
bsalomonedc743a2016-06-01 09:42:31 -07003359 return true;
3360}
bsalomon21af9ca2016-08-25 12:29:23 -07003361
Brian Salomone4949402018-04-26 15:22:04 -04003362bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3363 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3364 // This gets converted to an oval.
3365 return true;
3366 }
3367 if (useCenter) {
3368 // This is a pie wedge. It's convex if the angle is <= 180.
3369 return SkScalarAbs(sweepAngle) <= 180.f;
3370 }
3371 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3372 // to a secant, i.e. convex.
3373 return SkScalarAbs(sweepAngle) <= 360.f;
3374}
3375
bsalomon21af9ca2016-08-25 12:29:23 -07003376void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3377 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3378 SkASSERT(!oval.isEmpty());
3379 SkASSERT(sweepAngle);
3380
3381 path->reset();
3382 path->setIsVolatile(true);
3383 path->setFillType(SkPath::kWinding_FillType);
3384 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3385 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003386 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003387 return;
3388 }
3389 if (useCenter) {
3390 path->moveTo(oval.centerX(), oval.centerY());
3391 }
Brian Salomone4949402018-04-26 15:22:04 -04003392 auto firstDir =
3393 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3394 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003395 // Arc to mods at 360 and drawArc is not supposed to.
3396 bool forceMoveTo = !useCenter;
3397 while (sweepAngle <= -360.f) {
3398 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3399 startAngle -= 180.f;
3400 path->arcTo(oval, startAngle, -180.f, false);
3401 startAngle -= 180.f;
3402 forceMoveTo = false;
3403 sweepAngle += 360.f;
3404 }
3405 while (sweepAngle >= 360.f) {
3406 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3407 startAngle += 180.f;
3408 path->arcTo(oval, startAngle, 180.f, false);
3409 startAngle += 180.f;
3410 forceMoveTo = false;
3411 sweepAngle -= 360.f;
3412 }
3413 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3414 if (useCenter) {
3415 path->close();
3416 }
Brian Salomone4949402018-04-26 15:22:04 -04003417 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3418 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003419}
Mike Reed0d7dac82017-02-02 17:45:56 -08003420
3421///////////////////////////////////////////////////////////////////////////////////////////////////
3422#include "SkNx.h"
3423
3424static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3425 SkScalar ts[2];
3426 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3427 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3428 SkASSERT(n >= 0 && n <= 2);
3429 for (int i = 0; i < n; ++i) {
3430 extremas[i] = SkEvalQuadAt(src, ts[i]);
3431 }
3432 extremas[n] = src[2];
3433 return n + 1;
3434}
3435
3436static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3437 SkConic conic(src[0], src[1], src[2], w);
3438 SkScalar ts[2];
3439 int n = conic.findXExtrema(ts);
3440 n += conic.findYExtrema(&ts[n]);
3441 SkASSERT(n >= 0 && n <= 2);
3442 for (int i = 0; i < n; ++i) {
3443 extremas[i] = conic.evalAt(ts[i]);
3444 }
3445 extremas[n] = src[2];
3446 return n + 1;
3447}
3448
3449static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3450 SkScalar ts[4];
3451 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3452 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3453 SkASSERT(n >= 0 && n <= 4);
3454 for (int i = 0; i < n; ++i) {
3455 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3456 }
3457 extremas[n] = src[3];
3458 return n + 1;
3459}
3460
Mike Reed8d3196b2017-02-03 11:34:13 -05003461SkRect SkPath::computeTightBounds() const {
3462 if (0 == this->countVerbs()) {
3463 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003464 }
3465
Mike Reed8d3196b2017-02-03 11:34:13 -05003466 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3467 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003468 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003469
Mike Reed0d7dac82017-02-02 17:45:56 -08003470 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3471 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003472 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003473
3474 // initial with the first MoveTo, so we don't have to check inside the switch
3475 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003476 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003477 for (;;) {
3478 int count = 0;
3479 switch (iter.next(pts)) {
3480 case SkPath::kMove_Verb:
3481 extremas[0] = pts[0];
3482 count = 1;
3483 break;
3484 case SkPath::kLine_Verb:
3485 extremas[0] = pts[1];
3486 count = 1;
3487 break;
3488 case SkPath::kQuad_Verb:
3489 count = compute_quad_extremas(pts, extremas);
3490 break;
3491 case SkPath::kConic_Verb:
3492 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3493 break;
3494 case SkPath::kCubic_Verb:
3495 count = compute_cubic_extremas(pts, extremas);
3496 break;
3497 case SkPath::kClose_Verb:
3498 break;
3499 case SkPath::kDone_Verb:
3500 goto DONE;
3501 }
3502 for (int i = 0; i < count; ++i) {
3503 Sk2s tmp = from_point(extremas[i]);
3504 min = Sk2s::Min(min, tmp);
3505 max = Sk2s::Max(max, tmp);
3506 }
3507 }
3508DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003509 SkRect bounds;
3510 min.store((SkPoint*)&bounds.fLeft);
3511 max.store((SkPoint*)&bounds.fRight);
3512 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003513}
Cary Clarkdf429f32017-11-08 11:44:31 -05003514
3515bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3516 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3517}
3518
3519bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3520 const SkPoint& p3, bool exact) {
3521 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3522 SkPointPriv::EqualsWithinTolerance(p2, p3);
3523}
3524
3525bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3526 const SkPoint& p3, const SkPoint& p4, bool exact) {
3527 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3528 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3529 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3530 SkPointPriv::EqualsWithinTolerance(p3, p4);
3531}