blob: 5dcca6fff11af86f7c23c6aca4bf432a4a79b74c [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070014#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000016#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
Mike Reeda99b6ce2017-02-04 11:04:26 -050018static float poly_eval(float A, float B, float C, float t) {
19 return (A * t + B) * t + C;
20}
21
22static float poly_eval(float A, float B, float C, float D, float t) {
23 return ((A * t + B) * t + C) * t + D;
24}
25
reed@android.com8a1c16f2008-12-17 15:59:43 +000026////////////////////////////////////////////////////////////////////////////
27
reed@google.com3563c9e2011-11-14 19:34:57 +000028/**
29 * Path.bounds is defined to be the bounds of all the control points.
30 * If we called bounds.join(r) we would skip r if r was empty, which breaks
31 * our promise. Hence we have a custom joiner that doesn't look at emptiness
32 */
33static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
34 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
35 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
36 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
37 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
38}
39
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000040static bool is_degenerate(const SkPath& path) {
41 SkPath::Iter iter(path, false);
42 SkPoint pts[4];
43 return SkPath::kDone_Verb == iter.next(pts);
44}
45
bsalomon@google.com30c174b2012-11-13 14:36:42 +000046class SkAutoDisableDirectionCheck {
47public:
48 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070049 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050 }
51
52 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070053 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000054 }
55
56private:
reed026beb52015-06-10 14:23:15 -070057 SkPath* fPath;
58 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000059};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000060#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000061
reed@android.com8a1c16f2008-12-17 15:59:43 +000062/* This guy's constructor/destructor bracket a path editing operation. It is
63 used when we know the bounds of the amount we are going to add to the path
64 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000065
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000067 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000068 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000069
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000070 It also notes if the path was originally degenerate, and if so, sets
71 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000072 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000073 */
74class SkAutoPathBoundsUpdate {
75public:
76 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
77 this->init(path);
78 }
79
80 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
81 SkScalar right, SkScalar bottom) {
82 fRect.set(left, top, right, bottom);
83 this->init(path);
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000087 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
88 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 if (fEmpty || fHasValidBounds) {
90 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000091 }
92 }
reed@google.comabf15c12011-01-18 20:35:51 +000093
reed@android.com8a1c16f2008-12-17 15:59:43 +000094private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000095 SkPath* fPath;
96 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000097 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000098 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000099 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000100
reed@android.com6b82d1a2009-06-03 02:35:01 +0000101 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 // Cannot use fRect for our bounds unless we know it is sorted
103 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000105 // Mark the path's bounds as dirty if (1) they are, or (2) the path
106 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000107 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 if (fHasValidBounds && !fEmpty) {
110 joinNoEmptyChecks(&fRect, fPath->getBounds());
111 }
112 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 }
114};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000115#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117////////////////////////////////////////////////////////////////////////////
118
119/*
120 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000121 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
123
124 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000125 1. If we encounter degenerate segments, remove them
126 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
127 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
128 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129*/
130
131////////////////////////////////////////////////////////////////////////////
132
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000133// flag to require a moveTo if we begin with something else, like lineTo etc.
134#define INITIAL_LASTMOVETOINDEX_VALUE ~0
135
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000136SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800137 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000138 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700139 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140}
141
142void SkPath::resetFields() {
143 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000144 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000146 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700147 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000148
149 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700150 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000151}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000154 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 this->copyFields(that);
156 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157}
158
159SkPath::~SkPath() {
160 SkDEBUGCODE(this->validate();)
161}
162
bungeman@google.coma5809a32013-06-21 15:13:34 +0000163SkPath& SkPath::operator=(const SkPath& that) {
164 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166 if (this != &that) {
167 fPathRef.reset(SkRef(that.fPathRef.get()));
168 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 }
170 SkDEBUGCODE(this->validate();)
171 return *this;
172}
173
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174void SkPath::copyFields(const SkPath& that) {
175 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000176 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700179 // Simulate fFirstDirection = that.fFirstDirection;
180 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182}
183
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000184bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185 // note: don't need to look at isConvex or bounds, since just comparing the
186 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000187 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000188 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000189}
190
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000192 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700193 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000194 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700197 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
198 uint8_t temp = fFirstDirection;
199 fFirstDirection.store(that.fFirstDirection.load());
200 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700201 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
caryclark8e7b19d2016-02-18 04:11:48 -0800205bool SkPath::isInterpolatable(const SkPath& compare) const {
206 int count = fPathRef->countVerbs();
207 if (count != compare.fPathRef->countVerbs()) {
208 return false;
209 }
210 if (!count) {
211 return true;
212 }
213 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
214 count)) {
215 return false;
216 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800217 return !fPathRef->countWeights() ||
218 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800219 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
220}
221
222bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
223 int verbCount = fPathRef->countVerbs();
224 if (verbCount != ending.fPathRef->countVerbs()) {
225 return false;
226 }
caryclarka1105382016-02-18 06:13:25 -0800227 if (!verbCount) {
228 return true;
229 }
caryclark8e7b19d2016-02-18 04:11:48 -0800230 out->reset();
231 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700232 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800233 return true;
234}
235
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000236static inline bool check_edge_against_rect(const SkPoint& p0,
237 const SkPoint& p1,
238 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700239 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240 const SkPoint* edgeBegin;
241 SkVector v;
reed026beb52015-06-10 14:23:15 -0700242 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000243 v = p1 - p0;
244 edgeBegin = &p0;
245 } else {
246 v = p0 - p1;
247 edgeBegin = &p1;
248 }
249 if (v.fX || v.fY) {
250 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500251 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
252 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
253 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
254 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000255 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
256 return false;
257 }
258 }
259 return true;
260}
261
262bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
263 // This only handles non-degenerate convex paths currently.
264 if (kConvex_Convexity != this->getConvexity()) {
265 return false;
266 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000267
reed026beb52015-06-10 14:23:15 -0700268 SkPathPriv::FirstDirection direction;
269 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 return false;
271 }
272
273 SkPoint firstPt;
274 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500275 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000276 SkPath::Verb verb;
277 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400278 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281
Lee Salzmana19f0242017-01-12 13:06:21 -0500282 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000283 int nextPt = -1;
284 switch (verb) {
285 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000286 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 SkDEBUGCODE(++moveCnt);
288 firstPt = prevPt = pts[0];
289 break;
290 case kLine_Verb:
291 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400293 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 break;
295 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000296 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400298 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 nextPt = 2;
300 break;
301 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 nextPt = 3;
305 break;
306 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000308 break;
309 default:
310 SkDEBUGFAIL("unknown verb");
311 }
312 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800313 if (SkPath::kConic_Verb == verb) {
314 SkConic orig;
315 orig.set(pts, iter.conicWeight());
316 SkPoint quadPts[5];
317 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800318 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800319
320 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
321 return false;
322 }
323 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
324 return false;
325 }
326 } else {
327 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
328 return false;
329 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000330 }
331 prevPt = pts[nextPt];
332 }
333 }
334
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400335 if (segmentCount) {
336 return check_edge_against_rect(prevPt, firstPt, rect, direction);
337 }
338 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000339}
340
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000341uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000342 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800343#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000344 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
345 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
346#endif
347 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000348}
djsollen@google.come63793a2012-03-21 15:39:03 +0000349
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350void SkPath::reset() {
351 SkDEBUGCODE(this->validate();)
352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000353 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000354 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
357void SkPath::rewind() {
358 SkDEBUGCODE(this->validate();)
359
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000361 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362}
363
fsb1475b02016-01-20 09:51:07 -0800364bool SkPath::isLastContourClosed() const {
365 int verbCount = fPathRef->countVerbs();
366 if (0 == verbCount) {
367 return false;
368 }
369 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
370}
371
reed@google.com7e6c4d12012-05-10 14:05:43 +0000372bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000373 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000374
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000375 if (2 == verbCount) {
376 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
377 if (kLine_Verb == fPathRef->atVerb(1)) {
378 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000379 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000380 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000381 line[0] = pts[0];
382 line[1] = pts[1];
383 }
384 return true;
385 }
386 }
387 return false;
388}
389
caryclark@google.comf1316942011-07-26 19:54:45 +0000390/*
391 Determines if path is a rect by keeping track of changes in direction
392 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000393
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 The direction is computed such that:
395 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000396 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000397 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000398 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400A rectangle cycles up/right/down/left or up/left/down/right.
401
402The test fails if:
403 The path is closed, and followed by a line.
404 A second move creates a new endpoint.
405 A diagonal line is parsed.
406 There's more than four changes of direction.
407 There's a discontinuity on the line (e.g., a move in the middle)
408 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 The path contains a quadratic or cubic.
410 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000411 *The rectangle doesn't complete a cycle.
412 *The final point isn't equal to the first point.
413
414 *These last two conditions we relax if we have a 3-edge path that would
415 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000416
caryclark@google.comf1316942011-07-26 19:54:45 +0000417It's OK if the path has:
418 Several colinear line segments composing a rectangle side.
419 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000420
caryclark@google.comf1316942011-07-26 19:54:45 +0000421The direction takes advantage of the corners found since opposite sides
422must travel in opposite directions.
423
424FIXME: Allow colinear quads and cubics to be treated like lines.
425FIXME: If the API passes fill-only, return true if the filled stroke
426 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000427
428 first,last,next direction state-machine:
429 0x1 is set if the segment is horizontal
430 0x2 is set if the segment is moving to the right or down
431 thus:
432 two directions are opposites iff (dirA ^ dirB) == 0x2
433 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000434
caryclark@google.comf1316942011-07-26 19:54:45 +0000435 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000436static int rect_make_dir(SkScalar dx, SkScalar dy) {
437 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
438}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000439bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
440 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 int corners = 0;
442 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700444 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000445 first.set(0, 0);
446 last.set(0, 0);
447 int firstDirection = 0;
448 int lastDirection = 0;
449 int nextDirection = 0;
450 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700452 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000453 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000454 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700455 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
456 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000458 savePts = pts;
459 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700461 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 case kLine_Verb: {
463 SkScalar left = last.fX;
464 SkScalar top = last.fY;
465 SkScalar right = pts->fX;
466 SkScalar bottom = pts->fY;
467 ++pts;
468 if (left != right && top != bottom) {
469 return false; // diagonal
470 }
471 if (left == right && top == bottom) {
472 break; // single point on side OK
473 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000474 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 if (0 == corners) {
476 firstDirection = nextDirection;
477 first = last;
478 last = pts[-1];
479 corners = 1;
480 closedOrMoved = false;
481 break;
482 }
483 if (closedOrMoved) {
484 return false; // closed followed by a line
485 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000486 if (autoClose && nextDirection == firstDirection) {
487 break; // colinear with first
488 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 closedOrMoved = autoClose;
490 if (lastDirection != nextDirection) {
491 if (++corners > 4) {
492 return false; // too many direction changes
493 }
494 }
495 last = pts[-1];
496 if (lastDirection == nextDirection) {
497 break; // colinear segment
498 }
499 // Possible values for corners are 2, 3, and 4.
500 // When corners == 3, nextDirection opposes firstDirection.
501 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000502 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
504 if ((directionCycle ^ turn) != nextDirection) {
505 return false; // direction didn't follow cycle
506 }
507 break;
508 }
509 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000510 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 case kCubic_Verb:
512 return false; // quadratic, cubic not allowed
513 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700514 if (allowPartial && !autoClose && firstDirection) {
515 insertClose = true;
516 *currVerb -= 1; // try move again afterwards
517 goto addMissingClose;
518 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 last = *pts++;
520 closedOrMoved = true;
521 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000522 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000523 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000524 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700528addMissingClose:
529 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 }
531 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000532 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000533 if (!result) {
534 // check if we are just an incomplete rectangle, in which case we can
535 // return true, but not claim to be closed.
536 // e.g.
537 // 3 sided rectangle
538 // 4 sided but the last edge is not long enough to reach the start
539 //
540 SkScalar closeX = first.x() - last.x();
541 SkScalar closeY = first.y() - last.y();
542 if (closeX && closeY) {
543 return false; // we're diagonal, abort (can we ever reach this?)
544 }
545 int closeDirection = rect_make_dir(closeX, closeY);
546 // make sure the close-segment doesn't double-back on itself
547 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
548 result = true;
549 autoClose = false; // we are not closed
550 }
551 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000552 if (savePts) {
553 *ptsPtr = savePts;
554 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000555 if (result && isClosed) {
556 *isClosed = autoClose;
557 }
558 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000559 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000560 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return result;
562}
563
robertphillips4f662e62014-12-29 14:06:51 -0800564bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000565 SkDEBUGCODE(this->validate();)
566 int currVerb = 0;
567 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800568 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800569 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800570 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000571 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800572 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800573 int32_t num = SkToS32(pts - first);
574 if (num) {
575 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800576 } else {
577 // 'pts' isn't updated for open rects
578 *rect = this->getBounds();
579 }
580 }
581 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000582}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000583
caryclark95bc5f32015-04-08 08:34:15 -0700584bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000585 SkDEBUGCODE(this->validate();)
586 int currVerb = 0;
587 const SkPoint* pts = fPathRef->points();
588 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000589 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700590 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 return false;
592 }
593 const SkPoint* last = pts;
594 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700595 bool isClosed;
596 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000597 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700598 if (!isClosed) {
599 pts = fPathRef->points() + fPathRef->countPoints();
600 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000601 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602 if (testRects[0].contains(testRects[1])) {
603 if (rects) {
604 rects[0] = testRects[0];
605 rects[1] = testRects[1];
606 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 if (dirs) {
608 dirs[0] = testDirs[0];
609 dirs[1] = testDirs[1];
610 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 return true;
612 }
613 if (testRects[1].contains(testRects[0])) {
614 if (rects) {
615 rects[0] = testRects[1];
616 rects[1] = testRects[0];
617 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000618 if (dirs) {
619 dirs[0] = testDirs[1];
620 dirs[1] = testDirs[0];
621 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000622 return true;
623 }
624 }
625 return false;
626}
627
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000628int SkPath::countPoints() const {
629 return fPathRef->countPoints();
630}
631
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000632int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 SkDEBUGCODE(this->validate();)
634
635 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000636 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800638 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640}
641
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000642SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
644 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000645 }
646 return SkPoint::Make(0, 0);
647}
648
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649int SkPath::countVerbs() const {
650 return fPathRef->countVerbs();
651}
652
653static inline void copy_verbs_reverse(uint8_t* inorderDst,
654 const uint8_t* reversedSrc,
655 int count) {
656 for (int i = 0; i < count; ++i) {
657 inorderDst[i] = reversedSrc[~i];
658 }
659}
660
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000661int SkPath::getVerbs(uint8_t dst[], int max) const {
662 SkDEBUGCODE(this->validate();)
663
664 SkASSERT(max >= 0);
665 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 int count = SkMin32(max, fPathRef->countVerbs());
667 copy_verbs_reverse(dst, fPathRef->verbs(), count);
668 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000669}
670
reed@google.com294dd7b2011-10-11 11:58:32 +0000671bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 SkDEBUGCODE(this->validate();)
673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000674 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000675 if (count > 0) {
676 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000679 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000681 if (lastPt) {
682 lastPt->set(0, 0);
683 }
684 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685}
686
caryclarkaec25102015-04-29 08:28:30 -0700687void SkPath::setPt(int index, SkScalar x, SkScalar y) {
688 SkDEBUGCODE(this->validate();)
689
690 int count = fPathRef->countPoints();
691 if (count <= index) {
692 return;
693 } else {
694 SkPathRef::Editor ed(&fPathRef);
695 ed.atPoint(index)->set(x, y);
696 }
697}
698
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699void SkPath::setLastPt(SkScalar x, SkScalar y) {
700 SkDEBUGCODE(this->validate();)
701
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000702 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 if (count == 0) {
704 this->moveTo(x, y);
705 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000706 SkPathRef::Editor ed(&fPathRef);
707 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 }
709}
710
reed@google.com04863fa2011-05-15 04:08:24 +0000711void SkPath::setConvexity(Convexity c) {
712 if (fConvexity != c) {
713 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000714 }
715}
716
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717//////////////////////////////////////////////////////////////////////////////
718// Construction methods
719
reed026beb52015-06-10 14:23:15 -0700720#define DIRTY_AFTER_EDIT \
721 do { \
722 fConvexity = kUnknown_Convexity; \
723 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000724 } while (0)
725
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726void SkPath::incReserve(U16CPU inc) {
727 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000728 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729 SkDEBUGCODE(this->validate();)
730}
731
732void SkPath::moveTo(SkScalar x, SkScalar y) {
733 SkDEBUGCODE(this->validate();)
734
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000737 // remember our index
738 fLastMoveToIndex = fPathRef->countPoints();
739
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700741
742 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743}
744
745void SkPath::rMoveTo(SkScalar x, SkScalar y) {
746 SkPoint pt;
747 this->getLastPt(&pt);
748 this->moveTo(pt.fX + x, pt.fY + y);
749}
750
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000751void SkPath::injectMoveToIfNeeded() {
752 if (fLastMoveToIndex < 0) {
753 SkScalar x, y;
754 if (fPathRef->countVerbs() == 0) {
755 x = y = 0;
756 } else {
757 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
758 x = pt.fX;
759 y = pt.fY;
760 }
761 this->moveTo(x, y);
762 }
763}
764
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765void SkPath::lineTo(SkScalar x, SkScalar y) {
766 SkDEBUGCODE(this->validate();)
767
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000768 this->injectMoveToIfNeeded();
769
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000770 SkPathRef::Editor ed(&fPathRef);
771 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772
reed@google.comb54455e2011-05-16 14:16:04 +0000773 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774}
775
776void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000777 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 SkPoint pt;
779 this->getLastPt(&pt);
780 this->lineTo(pt.fX + x, pt.fY + y);
781}
782
783void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
784 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000785
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000786 this->injectMoveToIfNeeded();
787
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000788 SkPathRef::Editor ed(&fPathRef);
789 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 pts[0].set(x1, y1);
791 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
reed@google.comb54455e2011-05-16 14:16:04 +0000793 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794}
795
796void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000797 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 SkPoint pt;
799 this->getLastPt(&pt);
800 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
801}
802
reed@google.com277c3f82013-05-31 15:17:50 +0000803void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
804 SkScalar w) {
805 // check for <= 0 or NaN with this test
806 if (!(w > 0)) {
807 this->lineTo(x2, y2);
808 } else if (!SkScalarIsFinite(w)) {
809 this->lineTo(x1, y1);
810 this->lineTo(x2, y2);
811 } else if (SK_Scalar1 == w) {
812 this->quadTo(x1, y1, x2, y2);
813 } else {
814 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000815
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000816 this->injectMoveToIfNeeded();
817
reed@google.com277c3f82013-05-31 15:17:50 +0000818 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000819 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000820 pts[0].set(x1, y1);
821 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000822
reed@google.com277c3f82013-05-31 15:17:50 +0000823 DIRTY_AFTER_EDIT;
824 }
825}
826
827void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
828 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000829 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000830 SkPoint pt;
831 this->getLastPt(&pt);
832 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
833}
834
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
836 SkScalar x3, SkScalar y3) {
837 SkDEBUGCODE(this->validate();)
838
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839 this->injectMoveToIfNeeded();
840
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 SkPathRef::Editor ed(&fPathRef);
842 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 pts[0].set(x1, y1);
844 pts[1].set(x2, y2);
845 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846
reed@google.comb54455e2011-05-16 14:16:04 +0000847 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
851 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000852 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkPoint pt;
854 this->getLastPt(&pt);
855 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
856 pt.fX + x3, pt.fY + y3);
857}
858
859void SkPath::close() {
860 SkDEBUGCODE(this->validate();)
861
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000862 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000864 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 case kLine_Verb:
866 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000867 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 case kMove_Verb: {
870 SkPathRef::Editor ed(&fPathRef);
871 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000873 }
reed@google.com277c3f82013-05-31 15:17:50 +0000874 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000875 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000876 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000877 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000878 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000879 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 }
881 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000882
883 // signal that we need a moveTo to follow us (unless we're done)
884#if 0
885 if (fLastMoveToIndex >= 0) {
886 fLastMoveToIndex = ~fLastMoveToIndex;
887 }
888#else
889 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
890#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891}
892
893///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000894
fmalitac08d53e2015-11-17 09:53:29 -0800895namespace {
896
897template <unsigned N>
898class PointIterator {
899public:
900 PointIterator(SkPath::Direction dir, unsigned startIndex)
901 : fCurrent(startIndex % N)
902 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
903
904 const SkPoint& current() const {
905 SkASSERT(fCurrent < N);
906 return fPts[fCurrent];
907 }
908
909 const SkPoint& next() {
910 fCurrent = (fCurrent + fAdvance) % N;
911 return this->current();
912 }
913
914protected:
915 SkPoint fPts[N];
916
917private:
918 unsigned fCurrent;
919 unsigned fAdvance;
920};
921
922class RectPointIterator : public PointIterator<4> {
923public:
924 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
925 : PointIterator(dir, startIndex) {
926
927 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
928 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
929 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
930 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
931 }
932};
933
934class OvalPointIterator : public PointIterator<4> {
935public:
936 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
937 : PointIterator(dir, startIndex) {
938
939 const SkScalar cx = oval.centerX();
940 const SkScalar cy = oval.centerY();
941
942 fPts[0] = SkPoint::Make(cx, oval.fTop);
943 fPts[1] = SkPoint::Make(oval.fRight, cy);
944 fPts[2] = SkPoint::Make(cx, oval.fBottom);
945 fPts[3] = SkPoint::Make(oval.fLeft, cy);
946 }
947};
948
949class RRectPointIterator : public PointIterator<8> {
950public:
951 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
952 : PointIterator(dir, startIndex) {
953
954 const SkRect& bounds = rrect.getBounds();
955 const SkScalar L = bounds.fLeft;
956 const SkScalar T = bounds.fTop;
957 const SkScalar R = bounds.fRight;
958 const SkScalar B = bounds.fBottom;
959
960 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
961 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
962 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
963 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
964 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
965 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
966 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
967 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
968 }
969};
970
971} // anonymous namespace
972
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000973static void assert_known_direction(int dir) {
974 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
975}
976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800978 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979}
980
981void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
982 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800983 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
984}
985
986void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000987 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700988 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800989 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000990 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800991 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000992
fmalitac08d53e2015-11-17 09:53:29 -0800993 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994
fmalitac08d53e2015-11-17 09:53:29 -0800995 const int kVerbs = 5; // moveTo + 3x lineTo + close
996 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997
fmalitac08d53e2015-11-17 09:53:29 -0800998 RectPointIterator iter(rect, dir, startIndex);
999
1000 this->moveTo(iter.current());
1001 this->lineTo(iter.next());
1002 this->lineTo(iter.next());
1003 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001005
1006 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007}
1008
reed@google.com744faba2012-05-29 19:54:52 +00001009void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1010 SkDEBUGCODE(this->validate();)
1011 if (count <= 0) {
1012 return;
1013 }
1014
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001015 fLastMoveToIndex = fPathRef->countPoints();
1016
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001017 // +close makes room for the extra kClose_Verb
1018 SkPathRef::Editor ed(&fPathRef, count+close, count);
1019
1020 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001021 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001022 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1023 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001024 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001025
reed@google.com744faba2012-05-29 19:54:52 +00001026 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001027 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001028 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001029 }
1030
reed@google.com744faba2012-05-29 19:54:52 +00001031 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001032 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001033}
1034
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001035#include "SkGeometry.h"
1036
reedf90ea012015-01-29 12:03:58 -08001037static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1038 SkPoint* pt) {
1039 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001040 // Chrome uses this path to move into and out of ovals. If not
1041 // treated as a special case the moves can distort the oval's
1042 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001043 pt->set(oval.fRight, oval.centerY());
1044 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045 } else if (0 == oval.width() && 0 == oval.height()) {
1046 // Chrome will sometimes create 0 radius round rects. Having degenerate
1047 // quad segments in the path prevents the path from being recognized as
1048 // a rect.
1049 // TODO: optimizing the case where only one of width or height is zero
1050 // should also be considered. This case, however, doesn't seem to be
1051 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001052 pt->set(oval.fRight, oval.fTop);
1053 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001054 }
reedf90ea012015-01-29 12:03:58 -08001055 return false;
1056}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057
reedd5d27d92015-02-09 13:54:43 -08001058// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1059//
1060static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1061 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1062 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1063 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
1065 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001066 loss in radians-conversion and/or sin/cos, we may end up with coincident
1067 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1068 of drawing a nearly complete circle (good).
1069 e.g. canvas.drawArc(0, 359.99, ...)
1070 -vs- canvas.drawArc(0, 359.9, ...)
1071 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072 */
reedd5d27d92015-02-09 13:54:43 -08001073 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074 SkScalar sw = SkScalarAbs(sweepAngle);
1075 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1076 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1077 // make a guess at a tiny angle (in radians) to tweak by
1078 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1079 // not sure how much will be enough, so we use a loop
1080 do {
1081 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001082 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1083 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 }
1085 }
reedd5d27d92015-02-09 13:54:43 -08001086 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1087}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001088
reed9e779d42015-02-17 11:43:14 -08001089/**
1090 * If this returns 0, then the caller should just line-to the singlePt, else it should
1091 * ignore singlePt and append the specified number of conics.
1092 */
reedd5d27d92015-02-09 13:54:43 -08001093static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001094 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1095 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001096 SkMatrix matrix;
1097
1098 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1099 matrix.postTranslate(oval.centerX(), oval.centerY());
1100
reed9e779d42015-02-17 11:43:14 -08001101 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1102 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001103 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001104 }
1105 return count;
reedd5d27d92015-02-09 13:54:43 -08001106}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001107
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110 SkRRect rrect;
1111 rrect.setRectRadii(rect, (const SkVector*) radii);
1112 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113}
1114
reed@google.com4ed0fb72012-12-12 20:48:18 +00001115void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001116 // legacy start indices: 6 (CW) and 7(CCW)
1117 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1118}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119
fmalitac08d53e2015-11-17 09:53:29 -08001120void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1121 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122
fmalitac08d53e2015-11-17 09:53:29 -08001123 if (rrect.isEmpty()) {
1124 return;
reed1b28a3a2014-12-17 14:42:09 -08001125 }
fmalitac08d53e2015-11-17 09:53:29 -08001126
caryclarkda707bf2015-11-19 14:47:43 -08001127 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001128 const SkRect& bounds = rrect.getBounds();
1129
1130 if (rrect.isRect()) {
1131 // degenerate(rect) => radii points are collapsing
1132 this->addRect(bounds, dir, (startIndex + 1) / 2);
1133 } else if (rrect.isOval()) {
1134 // degenerate(oval) => line points are collapsing
1135 this->addOval(bounds, dir, startIndex / 2);
1136 } else {
1137 fFirstDirection = this->hasOnlyMoveTos() ?
1138 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1139
1140 SkAutoPathBoundsUpdate apbu(this, bounds);
1141 SkAutoDisableDirectionCheck addc(this);
1142
1143 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1144 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1145 const SkScalar weight = SK_ScalarRoot2Over2;
1146
1147 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1148 const int kVerbs = startsWithConic
1149 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1150 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1151 this->incReserve(kVerbs);
1152
1153 RRectPointIterator rrectIter(rrect, dir, startIndex);
1154 // Corner iterator indices follow the collapsed radii model,
1155 // adjusted such that the start pt is "behind" the radii start pt.
1156 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1157 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1158
1159 this->moveTo(rrectIter.current());
1160 if (startsWithConic) {
1161 for (unsigned i = 0; i < 3; ++i) {
1162 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1163 this->lineTo(rrectIter.next());
1164 }
1165 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1166 // final lineTo handled by close().
1167 } else {
1168 for (unsigned i = 0; i < 4; ++i) {
1169 this->lineTo(rrectIter.next());
1170 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1171 }
1172 }
1173 this->close();
1174
caryclarkda707bf2015-11-19 14:47:43 -08001175 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001176 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001177
fmalitac08d53e2015-11-17 09:53:29 -08001178 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1179 }
1180
1181 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001182}
1183
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001184bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001185 int count = fPathRef->countVerbs();
1186 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1187 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001188 if (*verbs == kLine_Verb ||
1189 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001190 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191 *verbs == kCubic_Verb) {
1192 return false;
1193 }
1194 ++verbs;
1195 }
1196 return true;
1197}
1198
Brian Osmana2318572017-07-10 15:09:26 -04001199bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1200 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001201 if (count < 2) {
1202 return true;
1203 }
Brian Osmana2318572017-07-10 15:09:26 -04001204 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001205 const SkPoint& first = *pts;
1206 for (int index = 1; index < count; ++index) {
1207 if (first != pts[index]) {
1208 return false;
1209 }
1210 }
1211 return true;
1212}
1213
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001214void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1215 Direction dir) {
1216 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001217
humper@google.com75e3ca12013-04-08 21:44:11 +00001218 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001219 return;
1220 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001221
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001222 SkRRect rrect;
1223 rrect.setRectXY(rect, rx, ry);
1224 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001225}
1226
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001228 // legacy start index: 1
1229 this->addOval(oval, dir, 1);
1230}
1231
1232void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001233 assert_known_direction(dir);
1234
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235 /* If addOval() is called after previous moveTo(),
1236 this path is still marked as an oval. This is used to
1237 fit into WebKit's calling sequences.
1238 We can't simply check isEmpty() in this case, as additional
1239 moveTo() would mark the path non empty.
1240 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001241 bool isOval = hasOnlyMoveTos();
1242 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001243 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001244 } else {
reed026beb52015-06-10 14:23:15 -07001245 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001246 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001247
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001248 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkAutoPathBoundsUpdate apbu(this, oval);
1250
fmalitac08d53e2015-11-17 09:53:29 -08001251 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1252 const int kVerbs = 6; // moveTo + 4x conicTo + close
1253 this->incReserve(kVerbs);
1254
1255 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1256 // The corner iterator pts are tracking "behind" the oval/radii pts.
1257 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001258 const SkScalar weight = SK_ScalarRoot2Over2;
1259
fmalitac08d53e2015-11-17 09:53:29 -08001260 this->moveTo(ovalIter.current());
1261 for (unsigned i = 0; i < 4; ++i) {
1262 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001263 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265
fmalitac08d53e2015-11-17 09:53:29 -08001266 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1267
robertphillips@google.com466310d2013-12-03 16:43:54 +00001268 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001269
bsalomon78d58d12016-05-27 09:17:04 -07001270 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001271}
1272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1274 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001275 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 }
1277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1280 bool forceMoveTo) {
1281 if (oval.width() < 0 || oval.height() < 0) {
1282 return;
1283 }
1284
reedf90ea012015-01-29 12:03:58 -08001285 if (fPathRef->countVerbs() == 0) {
1286 forceMoveTo = true;
1287 }
1288
1289 SkPoint lonePt;
1290 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1291 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1292 return;
1293 }
1294
reedd5d27d92015-02-09 13:54:43 -08001295 SkVector startV, stopV;
1296 SkRotationDirection dir;
1297 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1298
reed9e779d42015-02-17 11:43:14 -08001299 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001300
1301 // At this point, we know that the arc is not a lone point, but startV == stopV
1302 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1303 // cannot handle it.
1304 if (startV == stopV) {
1305 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1306 SkScalar radiusX = oval.width() / 2;
1307 SkScalar radiusY = oval.height() / 2;
1308 // We cannot use SkScalarSinCos function in the next line because
1309 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1310 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001311 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001312 // make sin(endAngle) to be 0 which will then draw a dot.
1313 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1314 oval.centerY() + radiusY * sk_float_sin(endAngle));
1315 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1316 return;
1317 }
1318
reedd5d27d92015-02-09 13:54:43 -08001319 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001320 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001321 if (count) {
1322 this->incReserve(count * 2 + 1);
1323 const SkPoint& pt = conics[0].fPts[0];
1324 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1325 for (int i = 0; i < count; ++i) {
1326 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1327 }
reed9e779d42015-02-17 11:43:14 -08001328 } else {
1329 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001330 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331}
1332
caryclark55d49052016-01-23 05:07:04 -08001333// This converts the SVG arc to conics.
1334// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1335// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1336// See also SVG implementation notes:
1337// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1338// Note that arcSweep bool value is flipped from the original implementation.
1339void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1340 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001341 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001342 SkPoint srcPts[2];
1343 this->getLastPt(&srcPts[0]);
1344 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1345 // joining the endpoints.
1346 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1347 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001348 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001349 return;
1350 }
1351 // If the current point and target point for the arc are identical, it should be treated as a
1352 // zero length path. This ensures continuity in animations.
1353 srcPts[1].set(x, y);
1354 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001355 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001356 return;
1357 }
1358 rx = SkScalarAbs(rx);
1359 ry = SkScalarAbs(ry);
1360 SkVector midPointDistance = srcPts[0] - srcPts[1];
1361 midPointDistance *= 0.5f;
1362
1363 SkMatrix pointTransform;
1364 pointTransform.setRotate(-angle);
1365
1366 SkPoint transformedMidPoint;
1367 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1368 SkScalar squareRx = rx * rx;
1369 SkScalar squareRy = ry * ry;
1370 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1371 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1372
1373 // Check if the radii are big enough to draw the arc, scale radii if not.
1374 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1375 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1376 if (radiiScale > 1) {
1377 radiiScale = SkScalarSqrt(radiiScale);
1378 rx *= radiiScale;
1379 ry *= radiiScale;
1380 }
1381
1382 pointTransform.setScale(1 / rx, 1 / ry);
1383 pointTransform.preRotate(-angle);
1384
1385 SkPoint unitPts[2];
1386 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1387 SkVector delta = unitPts[1] - unitPts[0];
1388
1389 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1390 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1391
1392 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1393 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1394 scaleFactor = -scaleFactor;
1395 }
1396 delta.scale(scaleFactor);
1397 SkPoint centerPoint = unitPts[0] + unitPts[1];
1398 centerPoint *= 0.5f;
1399 centerPoint.offset(-delta.fY, delta.fX);
1400 unitPts[0] -= centerPoint;
1401 unitPts[1] -= centerPoint;
1402 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1403 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1404 SkScalar thetaArc = theta2 - theta1;
1405 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1406 thetaArc += SK_ScalarPI * 2;
1407 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1408 thetaArc -= SK_ScalarPI * 2;
1409 }
1410 pointTransform.setRotate(angle);
1411 pointTransform.preScale(rx, ry);
1412
1413 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1414 SkScalar thetaWidth = thetaArc / segments;
1415 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1416 if (!SkScalarIsFinite(t)) {
1417 return;
1418 }
1419 SkScalar startTheta = theta1;
1420 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1421 for (int i = 0; i < segments; ++i) {
1422 SkScalar endTheta = startTheta + thetaWidth;
1423 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1424
1425 unitPts[1].set(cosEndTheta, sinEndTheta);
1426 unitPts[1] += centerPoint;
1427 unitPts[0] = unitPts[1];
1428 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1429 SkPoint mapped[2];
1430 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1431 this->conicTo(mapped[0], mapped[1], w);
1432 startTheta = endTheta;
1433 }
1434}
1435
1436void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1437 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1438 SkPoint currentPoint;
1439 this->getLastPt(&currentPoint);
1440 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1441}
1442
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001443void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 if (oval.isEmpty() || 0 == sweepAngle) {
1445 return;
1446 }
1447
1448 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1449
1450 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001451 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1452 // See SkPath::addOval() docs.
1453 SkScalar startOver90 = startAngle / 90.f;
1454 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1455 SkScalar error = startOver90 - startOver90I;
1456 if (SkScalarNearlyEqual(error, 0)) {
1457 // Index 1 is at startAngle == 0.
1458 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1459 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1460 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1461 (unsigned) startIndex);
1462 return;
1463 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 }
bsalomon1978ce22016-05-31 10:42:16 -07001465 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466}
1467
1468/*
1469 Need to handle the case when the angle is sharp, and our computed end-points
1470 for the arc go behind pt1 and/or p2...
1471*/
reedc7789042015-01-29 12:59:11 -08001472void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001473 if (radius == 0) {
1474 this->lineTo(x1, y1);
1475 return;
1476 }
1477
1478 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001479
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 // need to know our prev pt so we can construct tangent vectors
1481 {
1482 SkPoint start;
1483 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001484 // Handle degenerate cases by adding a line to the first point and
1485 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 before.setNormalize(x1 - start.fX, y1 - start.fY);
1487 after.setNormalize(x2 - x1, y2 - y1);
1488 }
reed@google.comabf15c12011-01-18 20:35:51 +00001489
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 SkScalar cosh = SkPoint::DotProduct(before, after);
1491 SkScalar sinh = SkPoint::CrossProduct(before, after);
1492
1493 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001494 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 return;
1496 }
reed@google.comabf15c12011-01-18 20:35:51 +00001497
Mike Reeda99b6ce2017-02-04 11:04:26 -05001498 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499
Mike Reeda99b6ce2017-02-04 11:04:26 -05001500 SkScalar xx = x1 - dist * before.fX;
1501 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001502 after.setLength(dist);
1503 this->lineTo(xx, yy);
1504 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1505 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506}
1507
1508///////////////////////////////////////////////////////////////////////////////
1509
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001510void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 SkMatrix matrix;
1512
1513 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001514 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515}
1516
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001517void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001520 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 SkPoint pts[4];
1522 Verb verb;
1523
1524 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001525 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 while ((verb = iter.next(pts)) != kDone_Verb) {
1527 switch (verb) {
1528 case kMove_Verb:
1529 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001530 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1531 injectMoveToIfNeeded(); // In case last contour is closed
1532 this->lineTo(pts[0]);
1533 } else {
1534 this->moveTo(pts[0]);
1535 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 break;
1537 case kLine_Verb:
1538 proc(matrix, &pts[1], &pts[1], 1);
1539 this->lineTo(pts[1]);
1540 break;
1541 case kQuad_Verb:
1542 proc(matrix, &pts[1], &pts[1], 2);
1543 this->quadTo(pts[1], pts[2]);
1544 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001545 case kConic_Verb:
1546 proc(matrix, &pts[1], &pts[1], 2);
1547 this->conicTo(pts[1], pts[2], iter.conicWeight());
1548 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 case kCubic_Verb:
1550 proc(matrix, &pts[1], &pts[1], 3);
1551 this->cubicTo(pts[1], pts[2], pts[3]);
1552 break;
1553 case kClose_Verb:
1554 this->close();
1555 break;
1556 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001557 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001559 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 }
1561}
1562
1563///////////////////////////////////////////////////////////////////////////////
1564
reed@google.com277c3f82013-05-31 15:17:50 +00001565static int pts_in_verb(unsigned verb) {
1566 static const uint8_t gPtsInVerb[] = {
1567 1, // kMove
1568 1, // kLine
1569 2, // kQuad
1570 2, // kConic
1571 3, // kCubic
1572 0, // kClose
1573 0 // kDone
1574 };
1575
1576 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1577 return gPtsInVerb[verb];
1578}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580// ignore the last point of the 1st contour
1581void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001582 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1583 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 return;
1585 }
caryclark51c56782016-11-07 05:09:28 -08001586 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1587 SkASSERT(verbsEnd[0] == kMove_Verb);
1588 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1589 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590
caryclark51c56782016-11-07 05:09:28 -08001591 while (verbs < verbsEnd) {
1592 uint8_t v = *verbs++;
1593 pts -= pts_in_verb(v);
1594 switch (v) {
1595 case kMove_Verb:
1596 // if the path has multiple contours, stop after reversing the last
1597 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001599 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 break;
1601 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001602 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001604 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001605 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001606 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001608 this->cubicTo(pts[2], pts[1], pts[0]);
1609 break;
1610 case kClose_Verb:
1611 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 break;
1613 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001614 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 break;
1616 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 }
1618}
1619
reed@google.com63d73742012-01-10 15:33:12 +00001620void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001621 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001623 const SkPoint* pts = src.fPathRef->pointsEnd();
1624 // we will iterator through src's verbs backwards
1625 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1626 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001627 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001628
1629 bool needMove = true;
1630 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 while (verbs < verbsEnd) {
1632 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001633 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001634
1635 if (needMove) {
1636 --pts;
1637 this->moveTo(pts->fX, pts->fY);
1638 needMove = false;
1639 }
1640 pts -= n;
1641 switch (v) {
1642 case kMove_Verb:
1643 if (needClose) {
1644 this->close();
1645 needClose = false;
1646 }
1647 needMove = true;
1648 pts += 1; // so we see the point in "if (needMove)" above
1649 break;
1650 case kLine_Verb:
1651 this->lineTo(pts[0]);
1652 break;
1653 case kQuad_Verb:
1654 this->quadTo(pts[1], pts[0]);
1655 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001656 case kConic_Verb:
1657 this->conicTo(pts[1], pts[0], *--conicWeights);
1658 break;
reed@google.com63d73742012-01-10 15:33:12 +00001659 case kCubic_Verb:
1660 this->cubicTo(pts[2], pts[1], pts[0]);
1661 break;
1662 case kClose_Verb:
1663 needClose = true;
1664 break;
1665 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001666 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001667 }
1668 }
1669}
1670
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671///////////////////////////////////////////////////////////////////////////////
1672
1673void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1674 SkMatrix matrix;
1675
1676 matrix.setTranslate(dx, dy);
1677 this->transform(matrix, dst);
1678}
1679
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1681 int level = 2) {
1682 if (--level >= 0) {
1683 SkPoint tmp[7];
1684
1685 SkChopCubicAtHalf(pts, tmp);
1686 subdivide_cubic_to(path, &tmp[0], level);
1687 subdivide_cubic_to(path, &tmp[3], level);
1688 } else {
1689 path->cubicTo(pts[1], pts[2], pts[3]);
1690 }
1691}
1692
1693void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1694 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001695 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 dst = (SkPath*)this;
1697 }
1698
tomhudson@google.com8d430182011-06-06 19:11:19 +00001699 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 SkPath tmp;
1701 tmp.fFillType = fFillType;
1702
1703 SkPath::Iter iter(*this, false);
1704 SkPoint pts[4];
1705 SkPath::Verb verb;
1706
reed@google.com4a3b7142012-05-16 17:16:46 +00001707 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 switch (verb) {
1709 case kMove_Verb:
1710 tmp.moveTo(pts[0]);
1711 break;
1712 case kLine_Verb:
1713 tmp.lineTo(pts[1]);
1714 break;
1715 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001716 // promote the quad to a conic
1717 tmp.conicTo(pts[1], pts[2],
1718 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001720 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001721 tmp.conicTo(pts[1], pts[2],
1722 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001723 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 case kCubic_Verb:
1725 subdivide_cubic_to(&tmp, pts);
1726 break;
1727 case kClose_Verb:
1728 tmp.close();
1729 break;
1730 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001731 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 break;
1733 }
1734 }
1735
1736 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001737 SkPathRef::Editor ed(&dst->fPathRef);
1738 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001739 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001741 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1742
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001745 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001746 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001748
reed026beb52015-06-10 14:23:15 -07001749 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1750 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001751 } else {
1752 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001753 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1754 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001755 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001756 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1757 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001758 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001759 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001760 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001761 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001762 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001763 }
1764 }
1765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 SkDEBUGCODE(dst->validate();)
1767 }
1768}
1769
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770///////////////////////////////////////////////////////////////////////////////
1771///////////////////////////////////////////////////////////////////////////////
1772
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001774 kEmptyContour_SegmentState, // The current contour is empty. We may be
1775 // starting processing or we may have just
1776 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001777 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1778 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1779 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780};
1781
1782SkPath::Iter::Iter() {
1783#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001784 fPts = nullptr;
1785 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001788 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789#endif
1790 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001791 fVerbs = nullptr;
1792 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 fNeedClose = false;
1794}
1795
1796SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1797 this->setPath(path, forceClose);
1798}
1799
1800void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 fPts = path.fPathRef->points();
1802 fVerbs = path.fPathRef->verbs();
1803 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001804 fConicWeights = path.fPathRef->conicWeights();
1805 if (fConicWeights) {
1806 fConicWeights -= 1; // begin one behind
1807 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001809 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 fForceClose = SkToU8(forceClose);
1811 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001812 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813}
1814
1815bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001816 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 return false;
1818 }
1819 if (fForceClose) {
1820 return true;
1821 }
1822
1823 const uint8_t* verbs = fVerbs;
1824 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 if (kMove_Verb == *(verbs - 1)) {
1827 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 }
1829
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 while (verbs > stop) {
1831 // verbs points one beyond the current verb, decrement first.
1832 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 if (kMove_Verb == v) {
1834 break;
1835 }
1836 if (kClose_Verb == v) {
1837 return true;
1838 }
1839 }
1840 return false;
1841}
1842
1843SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001844 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001846 // A special case: if both points are NaN, SkPoint::operation== returns
1847 // false, but the iterator expects that they are treated as the same.
1848 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001849 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1850 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001851 return kClose_Verb;
1852 }
1853
reed@google.com9e25dbf2012-05-15 17:05:38 +00001854 pts[0] = fLastPt;
1855 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 fLastPt = fMoveTo;
1857 fCloseLine = true;
1858 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001859 } else {
1860 pts[0] = fMoveTo;
1861 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863}
1864
reed@google.com9e25dbf2012-05-15 17:05:38 +00001865const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 if (fSegmentState == kAfterMove_SegmentState) {
1867 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001869 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1872 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875}
1876
caryclarke8c56662015-07-14 11:19:26 -07001877void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 // We need to step over anything that will not move the current draw point
1879 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001880 const uint8_t* lastMoveVerb = nullptr;
1881 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001882 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 SkPoint lastPt = fLastPt;
1884 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 switch (verb) {
1887 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 // Keep a record of this most recent move
1889 lastMoveVerb = fVerbs;
1890 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001891 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001892 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001893 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 fPts++;
1895 break;
1896
1897 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001898 // A close when we are in a segment is always valid except when it
1899 // follows a move which follows a segment.
1900 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 return;
1902 }
1903 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 break;
1906
1907 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001908 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 if (lastMoveVerb) {
1910 fVerbs = lastMoveVerb;
1911 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001912 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 return;
1914 }
1915 return;
1916 }
1917 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 fPts++;
1920 break;
1921
reed@google.com277c3f82013-05-31 15:17:50 +00001922 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001924 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 if (lastMoveVerb) {
1926 fVerbs = lastMoveVerb;
1927 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001928 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 return;
1930 }
1931 return;
1932 }
1933 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001934 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001936 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 break;
1938
1939 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001940 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 if (lastMoveVerb) {
1942 fVerbs = lastMoveVerb;
1943 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001944 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 return;
1946 }
1947 return;
1948 }
1949 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fPts += 3;
1952 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001953
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001955 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 }
1957 }
1958}
1959
reed@google.com4a3b7142012-05-16 17:16:46 +00001960SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001961 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 // Close the curve if requested and if there is some curve to close
1965 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 return kLine_Verb;
1968 }
1969 fNeedClose = false;
1970 return kClose_Verb;
1971 }
1972 return kDone_Verb;
1973 }
1974
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 // fVerbs is one beyond the current verb, decrement first
1976 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001977 const SkPoint* SK_RESTRICT srcPts = fPts;
1978 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979
1980 switch (verb) {
1981 case kMove_Verb:
1982 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001983 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 verb = this->autoClose(pts);
1985 if (verb == kClose_Verb) {
1986 fNeedClose = false;
1987 }
1988 return (Verb)verb;
1989 }
1990 if (fVerbs == fVerbStop) { // might be a trailing moveto
1991 return kDone_Verb;
1992 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001993 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001994 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001996 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 fNeedClose = fForceClose;
1999 break;
2000 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002001 pts[0] = this->cons_moveTo();
2002 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 fLastPt = srcPts[0];
2004 fCloseLine = false;
2005 srcPts += 1;
2006 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002007 case kConic_Verb:
2008 fConicWeights += 1;
2009 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002011 pts[0] = this->cons_moveTo();
2012 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 fLastPt = srcPts[1];
2014 srcPts += 2;
2015 break;
2016 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 pts[0] = this->cons_moveTo();
2018 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 fLastPt = srcPts[2];
2020 srcPts += 3;
2021 break;
2022 case kClose_Verb:
2023 verb = this->autoClose(pts);
2024 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002025 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 } else {
2027 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002028 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002030 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 break;
2032 }
2033 fPts = srcPts;
2034 return (Verb)verb;
2035}
2036
2037///////////////////////////////////////////////////////////////////////////////
2038
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002040 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041*/
2042
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002043size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 SkDEBUGCODE(this->validate();)
2045
halcanary96fcdcc2015-08-27 07:41:13 -07002046 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002047 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002048 return SkAlign4(byteCount);
2049 }
2050
2051 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002052
robertphillips@google.com466310d2013-12-03 16:43:54 +00002053 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002054 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002055 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002056 (fIsVolatile << kIsVolatile_SerializationShift) |
2057 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002059 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002060 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002062 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002064 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002065 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066}
2067
Mike Reed41a930f2017-07-26 17:33:44 -04002068sk_sp<SkData> SkPath::serialize() const {
2069 size_t size = this->writeToMemory(nullptr);
2070 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2071 this->writeToMemory(data->writable_data());
2072 return data;
2073}
2074
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002075size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002076 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002078 int32_t packed;
2079 if (!buffer.readS32(&packed)) {
2080 return 0;
2081 }
2082
reed8f086022015-06-11 14:22:19 -07002083 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002084 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2085 return 0;
2086 }
mtklein1b249332015-07-07 12:21:21 -07002087
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002088 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002089 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002090 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002091 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002092 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002093 if (!pathRef) {
2094 return 0;
2095 }
2096
2097 fPathRef.reset(pathRef);
2098 SkDEBUGCODE(this->validate();)
2099 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002100
reed8f086022015-06-11 14:22:19 -07002101 // compatibility check
2102 if (version < kPathPrivFirstDirection_Version) {
2103 switch (dir) { // old values
2104 case 0:
2105 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2106 break;
2107 case 1:
2108 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2109 break;
2110 case 2:
2111 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2112 break;
2113 default:
2114 SkASSERT(false);
2115 }
2116 } else {
2117 fFirstDirection = dir;
2118 }
2119
ajumaf8aec582016-01-13 13:46:31 -08002120 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121}
2122
2123///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124
Ben Wagner4d1955c2017-03-10 13:08:15 -05002125#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002126#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002127#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002128
reed@google.com51bbe752013-01-17 22:07:50 +00002129static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002130 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002131 str->append(label);
2132 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002133
reed@google.com51bbe752013-01-17 22:07:50 +00002134 const SkScalar* values = &pts[0].fX;
2135 count *= 2;
2136
2137 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002138 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002139 if (i < count - 1) {
2140 str->append(", ");
2141 }
2142 }
reed@google.com277c3f82013-05-31 15:17:50 +00002143 if (conicWeight >= 0) {
2144 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002145 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002146 }
caryclark08fa28c2014-10-23 13:08:56 -07002147 str->append(");");
reede05fed02014-12-15 07:59:53 -08002148 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002149 str->append(" // ");
2150 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002151 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002152 if (i < count - 1) {
2153 str->append(", ");
2154 }
2155 }
2156 if (conicWeight >= 0) {
2157 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002158 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002159 }
2160 }
2161 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002162}
2163
caryclarke9562592014-09-15 09:26:09 -07002164void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002165 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 Iter iter(*this, forceClose);
2167 SkPoint pts[4];
2168 Verb verb;
2169
reed@google.com51bbe752013-01-17 22:07:50 +00002170 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002171 char const * const gFillTypeStrs[] = {
2172 "Winding",
2173 "EvenOdd",
2174 "InverseWinding",
2175 "InverseEvenOdd",
2176 };
2177 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2178 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002179 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 switch (verb) {
2181 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002182 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002185 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
2187 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002188 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002190 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002191 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002192 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002194 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 break;
2196 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002197 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002198 break;
2199 default:
2200 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2201 verb = kDone_Verb; // stop the loop
2202 break;
2203 }
caryclark1049f122015-04-20 08:31:59 -07002204 if (!wStream && builder.size()) {
2205 SkDebugf("%s", builder.c_str());
2206 builder.reset();
2207 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 }
caryclark66a5d8b2014-06-24 08:30:15 -07002209 if (wStream) {
2210 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002211 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212}
2213
reed@android.come522ca52009-11-23 20:10:41 +00002214void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002215 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002216}
2217
2218void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002219 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002220}
2221
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002222
Cary Clark0461e9f2017-08-25 15:13:38 -04002223bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002224 if ((fFillType & ~3) != 0) {
2225 return false;
2226 }
reed@google.comabf15c12011-01-18 20:35:51 +00002227
djsollen@google.com077348c2012-10-22 20:23:32 +00002228#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002229 if (!fBoundsIsDirty) {
2230 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002231
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002232 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002233 if (SkToBool(fIsFinite) != isFinite) {
2234 return false;
2235 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002236
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002237 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002238 // if we're empty, fBounds may be empty but translated, so we can't
2239 // necessarily compare to bounds directly
2240 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2241 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002242 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2243 return false;
2244 }
reed@android.come522ca52009-11-23 20:10:41 +00002245 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002246 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002247 if (!fBounds.isEmpty()) {
2248 return false;
2249 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002250 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002251 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002252 if (!fBounds.contains(bounds)) {
2253 return false;
2254 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002255 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002256 }
reed@android.come522ca52009-11-23 20:10:41 +00002257 }
2258 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002259#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002260 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002261}
reed@android.come522ca52009-11-23 20:10:41 +00002262
reed@google.com04863fa2011-05-15 04:08:24 +00002263///////////////////////////////////////////////////////////////////////////////
2264
reed@google.com0b7b9822011-05-16 12:29:27 +00002265static int sign(SkScalar x) { return x < 0; }
2266#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002267
robertphillipsc506e302014-09-16 09:43:31 -07002268enum DirChange {
2269 kLeft_DirChange,
2270 kRight_DirChange,
2271 kStraight_DirChange,
2272 kBackwards_DirChange,
2273
2274 kInvalid_DirChange
2275};
2276
2277
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002278static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002279 // The error epsilon was empirically derived; worse case round rects
2280 // with a mid point outset by 2x float epsilon in tests had an error
2281 // of 12.
2282 const int epsilon = 16;
2283 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2284 return false;
2285 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002286 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002287 int aBits = SkFloatAs2sCompliment(compA);
2288 int bBits = SkFloatAs2sCompliment(compB);
2289 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002290}
2291
caryclarkb4216502015-03-02 13:02:34 -08002292static bool approximately_zero_when_compared_to(double x, double y) {
2293 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002294}
2295
caryclarkb4216502015-03-02 13:02:34 -08002296
reed@google.com04863fa2011-05-15 04:08:24 +00002297// only valid for a single contour
2298struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299 Convexicator()
2300 : fPtCount(0)
2301 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002302 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002303 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002304 , fIsCurve(false)
2305 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002306 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 // warnings
djsollen2f124632016-04-29 13:53:05 -07002308 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002309 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002310 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002311 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002312 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002313
2314 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002315 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002316 }
2317
2318 SkPath::Convexity getConvexity() const { return fConvexity; }
2319
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002320 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002321 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002322
reed@google.com04863fa2011-05-15 04:08:24 +00002323 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002324 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002325 return;
2326 }
2327
2328 if (0 == fPtCount) {
2329 fCurrPt = pt;
2330 ++fPtCount;
2331 } else {
2332 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002333 SkScalar lengthSqd = vec.lengthSqd();
2334 if (!SkScalarIsFinite(lengthSqd)) {
2335 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002336 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002337 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002338 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002339 fCurrPt = pt;
2340 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002341 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002342 } else {
2343 SkASSERT(fPtCount > 2);
2344 this->addVec(vec);
2345 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002346
reed@google.com85b6e392011-05-15 20:25:17 +00002347 int sx = sign(vec.fX);
2348 int sy = sign(vec.fY);
2349 fDx += (sx != fSx);
2350 fDy += (sy != fSy);
2351 fSx = sx;
2352 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002353
reed@google.com85b6e392011-05-15 20:25:17 +00002354 if (fDx > 3 || fDy > 3) {
2355 fConvexity = SkPath::kConcave_Convexity;
2356 }
reed@google.com04863fa2011-05-15 04:08:24 +00002357 }
2358 }
2359 }
2360
2361 void close() {
2362 if (fPtCount > 2) {
2363 this->addVec(fFirstVec);
2364 }
2365 }
2366
caryclarkb4216502015-03-02 13:02:34 -08002367 DirChange directionChange(const SkVector& curVec) {
2368 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2369
2370 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2371 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2372 largest = SkTMax(largest, -smallest);
2373
2374 if (!almost_equal(largest, largest + cross)) {
2375 int sign = SkScalarSignAsInt(cross);
2376 if (sign) {
2377 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2378 }
2379 }
2380
2381 if (cross) {
2382 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2383 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2384 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2385 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2386 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2387 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2388 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2389 if (sign) {
2390 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2391 }
2392 }
2393 }
2394
2395 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2396 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2397 fLastVec.dot(curVec) < 0.0f) {
2398 return kBackwards_DirChange;
2399 }
2400
2401 return kStraight_DirChange;
2402 }
2403
Cary Clarkc8323aa2017-08-25 08:04:43 -04002404 bool hasBackwards() const {
2405 return fBackwards;
2406 }
caryclarkb4216502015-03-02 13:02:34 -08002407
caryclarkd3d1a982014-12-08 04:57:38 -08002408 bool isFinite() const {
2409 return fIsFinite;
2410 }
2411
caryclark5ccef572015-03-02 10:07:56 -08002412 void setCurve(bool isCurve) {
2413 fIsCurve = isCurve;
2414 }
2415
reed@google.com04863fa2011-05-15 04:08:24 +00002416private:
2417 void addVec(const SkVector& vec) {
2418 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002419 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002420 switch (dir) {
2421 case kLeft_DirChange: // fall through
2422 case kRight_DirChange:
2423 if (kInvalid_DirChange == fExpectedDir) {
2424 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002425 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2426 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002427 } else if (dir != fExpectedDir) {
2428 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002429 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002430 }
2431 fLastVec = vec;
2432 break;
2433 case kStraight_DirChange:
2434 break;
2435 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002436 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002437 // If any of the subsequent dir is non-backward, it'll be concave.
2438 // Otherwise, it's still convex.
2439 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002440 }
robertphillipsc506e302014-09-16 09:43:31 -07002441 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002442 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002443 break;
2444 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002445 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002446 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002447 }
2448 }
2449
caryclarkb4216502015-03-02 13:02:34 -08002450 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002451 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002452 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002453 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2454 // value with the current vec is deemed to be of a significant value.
2455 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002456 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002457 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002459 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002460 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002461 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002462 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002463 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002464};
2465
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002466SkPath::Convexity SkPath::internalGetConvexity() const {
2467 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002468 SkPoint pts[4];
2469 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002470 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002471
2472 int contourCount = 0;
2473 int count;
2474 Convexicator state;
2475
caryclarkd3d1a982014-12-08 04:57:38 -08002476 if (!isFinite()) {
2477 return kUnknown_Convexity;
2478 }
caryclarke8c56662015-07-14 11:19:26 -07002479 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002480 switch (verb) {
2481 case kMove_Verb:
2482 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002483 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002484 return kConcave_Convexity;
2485 }
2486 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002487 // fall through
2488 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002489 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002490 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002491 break;
caryclark5ccef572015-03-02 10:07:56 -08002492 case kQuad_Verb:
2493 // fall through
2494 case kConic_Verb:
2495 // fall through
2496 case kCubic_Verb:
2497 count = 2 + (kCubic_Verb == verb);
2498 // As an additional enhancement, this could set curve true only
2499 // if the curve is nonlinear
2500 state.setCurve(true);
2501 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002502 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002503 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002504 state.close();
2505 count = 0;
2506 break;
2507 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002508 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002509 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002510 return kConcave_Convexity;
2511 }
2512
2513 for (int i = 1; i <= count; i++) {
2514 state.addPt(pts[i]);
2515 }
2516 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002517 if (!state.isFinite()) {
2518 return kUnknown_Convexity;
2519 }
reed@google.com04863fa2011-05-15 04:08:24 +00002520 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002521 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002522 return kConcave_Convexity;
2523 }
2524 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002525 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002526 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002527 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2528 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2529 fConvexity = Convexity::kConcave_Convexity;
2530 } else {
2531 fFirstDirection = state.getFirstDirection();
2532 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002533 }
2534 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002535}
reed@google.com69a99432012-01-10 18:00:10 +00002536
2537///////////////////////////////////////////////////////////////////////////////
2538
2539class ContourIter {
2540public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002541 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002542
2543 bool done() const { return fDone; }
2544 // if !done() then these may be called
2545 int count() const { return fCurrPtCount; }
2546 const SkPoint* pts() const { return fCurrPt; }
2547 void next();
2548
2549private:
2550 int fCurrPtCount;
2551 const SkPoint* fCurrPt;
2552 const uint8_t* fCurrVerb;
2553 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002554 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002555 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002556 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002557};
2558
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002559ContourIter::ContourIter(const SkPathRef& pathRef) {
2560 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002561 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002562 fCurrPt = pathRef.points();
2563 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002564 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002565 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002566 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002567 this->next();
2568}
2569
2570void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002571 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002572 fDone = true;
2573 }
2574 if (fDone) {
2575 return;
2576 }
2577
2578 // skip pts of prev contour
2579 fCurrPt += fCurrPtCount;
2580
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002581 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002582 int ptCount = 1; // moveTo
2583 const uint8_t* verbs = fCurrVerb;
2584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002585 for (--verbs; verbs > fStopVerbs; --verbs) {
2586 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002587 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002588 goto CONTOUR_END;
2589 case SkPath::kLine_Verb:
2590 ptCount += 1;
2591 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002592 case SkPath::kConic_Verb:
2593 fCurrConicWeight += 1;
2594 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002595 case SkPath::kQuad_Verb:
2596 ptCount += 2;
2597 break;
2598 case SkPath::kCubic_Verb:
2599 ptCount += 3;
2600 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002601 case SkPath::kClose_Verb:
2602 break;
2603 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002604 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002605 break;
2606 }
2607 }
2608CONTOUR_END:
2609 fCurrPtCount = ptCount;
2610 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002611 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002612}
2613
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002614// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002615static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002616 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2617 // We may get 0 when the above subtracts underflow. We expect this to be
2618 // very rare and lazily promote to double.
2619 if (0 == cross) {
2620 double p0x = SkScalarToDouble(p0.fX);
2621 double p0y = SkScalarToDouble(p0.fY);
2622
2623 double p1x = SkScalarToDouble(p1.fX);
2624 double p1y = SkScalarToDouble(p1.fY);
2625
2626 double p2x = SkScalarToDouble(p2.fX);
2627 double p2y = SkScalarToDouble(p2.fY);
2628
2629 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2630 (p1y - p0y) * (p2x - p0x));
2631
2632 }
2633 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002634}
2635
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002637static int find_max_y(const SkPoint pts[], int count) {
2638 SkASSERT(count > 0);
2639 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002640 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002641 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002642 SkScalar y = pts[i].fY;
2643 if (y > max) {
2644 max = y;
2645 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002646 }
2647 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002648 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002649}
2650
reed@google.comcabaf1d2012-01-11 21:03:05 +00002651static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2652 int i = index;
2653 for (;;) {
2654 i = (i + inc) % n;
2655 if (i == index) { // we wrapped around, so abort
2656 break;
2657 }
2658 if (pts[index] != pts[i]) { // found a different point, success!
2659 break;
2660 }
2661 }
2662 return i;
2663}
2664
reed@google.comc1ea60a2012-01-31 15:15:36 +00002665/**
2666 * Starting at index, and moving forward (incrementing), find the xmin and
2667 * xmax of the contiguous points that have the same Y.
2668 */
2669static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2670 int* maxIndexPtr) {
2671 const SkScalar y = pts[index].fY;
2672 SkScalar min = pts[index].fX;
2673 SkScalar max = min;
2674 int minIndex = index;
2675 int maxIndex = index;
2676 for (int i = index + 1; i < n; ++i) {
2677 if (pts[i].fY != y) {
2678 break;
2679 }
2680 SkScalar x = pts[i].fX;
2681 if (x < min) {
2682 min = x;
2683 minIndex = i;
2684 } else if (x > max) {
2685 max = x;
2686 maxIndex = i;
2687 }
2688 }
2689 *maxIndexPtr = maxIndex;
2690 return minIndex;
2691}
2692
reed026beb52015-06-10 14:23:15 -07002693static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2694 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002695}
2696
reed@google.comac8543f2012-01-30 20:51:25 +00002697/*
2698 * We loop through all contours, and keep the computed cross-product of the
2699 * contour that contained the global y-max. If we just look at the first
2700 * contour, we may find one that is wound the opposite way (correctly) since
2701 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2702 * that is outer most (or at least has the global y-max) before we can consider
2703 * its cross product.
2704 */
reed026beb52015-06-10 14:23:15 -07002705bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002706 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2707 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002708 return true;
2709 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002710
2711 // don't want to pay the cost for computing this if it
2712 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002713 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2714 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002715 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002716 return false;
2717 }
reed@google.com69a99432012-01-10 18:00:10 +00002718
reed026beb52015-06-10 14:23:15 -07002719 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002720
reed@google.comac8543f2012-01-30 20:51:25 +00002721 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002722 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002723 SkScalar ymaxCross = 0;
2724
reed@google.com69a99432012-01-10 18:00:10 +00002725 for (; !iter.done(); iter.next()) {
2726 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002727 if (n < 3) {
2728 continue;
2729 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002730
reed@google.comcabaf1d2012-01-11 21:03:05 +00002731 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002732 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002733 int index = find_max_y(pts, n);
2734 if (pts[index].fY < ymax) {
2735 continue;
2736 }
2737
2738 // If there is more than 1 distinct point at the y-max, we take the
2739 // x-min and x-max of them and just subtract to compute the dir.
2740 if (pts[(index + 1) % n].fY == pts[index].fY) {
2741 int maxIndex;
2742 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2743 if (minIndex == maxIndex) {
2744 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002745 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002746 SkASSERT(pts[minIndex].fY == pts[index].fY);
2747 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2748 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2749 // we just subtract the indices, and let that auto-convert to
2750 // SkScalar, since we just want - or + to signal the direction.
2751 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002752 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002753 TRY_CROSSPROD:
2754 // Find a next and prev index to use for the cross-product test,
2755 // but we try to find pts that form non-zero vectors from pts[index]
2756 //
2757 // Its possible that we can't find two non-degenerate vectors, so
2758 // we have to guard our search (e.g. all the pts could be in the
2759 // same place).
2760
2761 // we pass n - 1 instead of -1 so we don't foul up % operator by
2762 // passing it a negative LH argument.
2763 int prev = find_diff_pt(pts, index, n, n - 1);
2764 if (prev == index) {
2765 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002766 continue;
2767 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002768 int next = find_diff_pt(pts, index, n, 1);
2769 SkASSERT(next != index);
2770 cross = cross_prod(pts[prev], pts[index], pts[next]);
2771 // if we get a zero and the points are horizontal, then we look at the spread in
2772 // x-direction. We really should continue to walk away from the degeneracy until
2773 // there is a divergence.
2774 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2775 // construct the subtract so we get the correct Direction below
2776 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002777 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002778 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002780 if (cross) {
2781 // record our best guess so far
2782 ymax = pts[index].fY;
2783 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002784 }
2785 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002786 if (ymaxCross) {
2787 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002788 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002789 return true;
2790 } else {
2791 return false;
2792 }
reed@google.comac8543f2012-01-30 20:51:25 +00002793}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794
2795///////////////////////////////////////////////////////////////////////////////
2796
caryclark9aacd902015-12-14 08:38:09 -08002797static bool between(SkScalar a, SkScalar b, SkScalar c) {
2798 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2799 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2800 return (a - b) * (c - b) <= 0;
2801}
2802
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2804 SkScalar t) {
2805 SkScalar A = c3 + 3*(c1 - c2) - c0;
2806 SkScalar B = 3*(c2 - c1 - c1 + c0);
2807 SkScalar C = 3*(c1 - c0);
2808 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002809 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810}
2811
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002812template <size_t N> static void find_minmax(const SkPoint pts[],
2813 SkScalar* minPtr, SkScalar* maxPtr) {
2814 SkScalar min, max;
2815 min = max = pts[0].fX;
2816 for (size_t i = 1; i < N; ++i) {
2817 min = SkMinScalar(min, pts[i].fX);
2818 max = SkMaxScalar(max, pts[i].fX);
2819 }
2820 *minPtr = min;
2821 *maxPtr = max;
2822}
2823
caryclark9cb5d752015-12-18 04:35:24 -08002824static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2825 if (start.fY == end.fY) {
2826 return between(start.fX, x, end.fX) && x != end.fX;
2827 } else {
2828 return x == start.fX && y == start.fY;
2829 }
2830}
2831
caryclark9aacd902015-12-14 08:38:09 -08002832static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002833 SkScalar y0 = pts[0].fY;
2834 SkScalar y3 = pts[3].fY;
2835
2836 int dir = 1;
2837 if (y0 > y3) {
2838 SkTSwap(y0, y3);
2839 dir = -1;
2840 }
2841 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002842 return 0;
2843 }
caryclark9cb5d752015-12-18 04:35:24 -08002844 if (checkOnCurve(x, y, pts[0], pts[3])) {
2845 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002846 return 0;
2847 }
caryclark9cb5d752015-12-18 04:35:24 -08002848 if (y == y3) {
2849 return 0;
2850 }
2851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 // quickreject or quickaccept
2853 SkScalar min, max;
2854 find_minmax<4>(pts, &min, &max);
2855 if (x < min) {
2856 return 0;
2857 }
2858 if (x > max) {
2859 return dir;
2860 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002863 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002864 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002865 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002866 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002867 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002868 if (SkScalarNearlyEqual(xt, x)) {
2869 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2870 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002871 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002872 }
2873 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002874 return xt < x ? dir : 0;
2875}
2876
caryclark9aacd902015-12-14 08:38:09 -08002877static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 SkPoint dst[10];
2879 int n = SkChopCubicAtYExtrema(pts, dst);
2880 int w = 0;
2881 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002882 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002883 }
2884 return w;
2885}
2886
caryclark9aacd902015-12-14 08:38:09 -08002887static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2888 SkASSERT(src);
2889 SkASSERT(t >= 0 && t <= 1);
2890 SkScalar src2w = src[2] * w;
2891 SkScalar C = src[0];
2892 SkScalar A = src[4] - 2 * src2w + C;
2893 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002894 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002895}
2896
2897
2898static double conic_eval_denominator(SkScalar w, SkScalar t) {
2899 SkScalar B = 2 * (w - 1);
2900 SkScalar C = 1;
2901 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002902 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002903}
2904
2905static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2906 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907 SkScalar y0 = pts[0].fY;
2908 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002910 int dir = 1;
2911 if (y0 > y2) {
2912 SkTSwap(y0, y2);
2913 dir = -1;
2914 }
caryclark9aacd902015-12-14 08:38:09 -08002915 if (y < y0 || y > y2) {
2916 return 0;
2917 }
caryclark9cb5d752015-12-18 04:35:24 -08002918 if (checkOnCurve(x, y, pts[0], pts[2])) {
2919 *onCurveCount += 1;
2920 return 0;
2921 }
caryclark9aacd902015-12-14 08:38:09 -08002922 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 return 0;
2924 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002925
caryclark9aacd902015-12-14 08:38:09 -08002926 SkScalar roots[2];
2927 SkScalar A = pts[2].fY;
2928 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2929 SkScalar C = pts[0].fY;
2930 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2931 B -= C; // B = b*w - w * yCept + yCept - a
2932 C -= y;
2933 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2934 SkASSERT(n <= 1);
2935 SkScalar xt;
2936 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002937 // zero roots are returned only when y0 == y
2938 // Need [0] if dir == 1
2939 // and [2] if dir == -1
2940 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002941 } else {
2942 SkScalar t = roots[0];
2943 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2944 }
2945 if (SkScalarNearlyEqual(xt, x)) {
2946 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2947 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002948 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002949 }
2950 }
2951 return xt < x ? dir : 0;
2952}
2953
2954static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2955 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2956 if (y0 == y1) {
2957 return true;
2958 }
2959 if (y0 < y1) {
2960 return y1 <= y2;
2961 } else {
2962 return y1 >= y2;
2963 }
2964}
2965
2966static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2967 int* onCurveCount) {
2968 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002969 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002970 // If the data points are very large, the conic may not be monotonic but may also
2971 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002972 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2973 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2974 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002975 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2976 }
2977 return w;
2978}
2979
2980static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2981 SkScalar y0 = pts[0].fY;
2982 SkScalar y2 = pts[2].fY;
2983
2984 int dir = 1;
2985 if (y0 > y2) {
2986 SkTSwap(y0, y2);
2987 dir = -1;
2988 }
2989 if (y < y0 || y > y2) {
2990 return 0;
2991 }
caryclark9cb5d752015-12-18 04:35:24 -08002992 if (checkOnCurve(x, y, pts[0], pts[2])) {
2993 *onCurveCount += 1;
2994 return 0;
2995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002997 return 0;
2998 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 // bounds check on X (not required. is it faster?)
3000#if 0
3001 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3002 return 0;
3003 }
3004#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003005
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 SkScalar roots[2];
3007 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3008 2 * (pts[1].fY - pts[0].fY),
3009 pts[0].fY - y,
3010 roots);
3011 SkASSERT(n <= 1);
3012 SkScalar xt;
3013 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003014 // zero roots are returned only when y0 == y
3015 // Need [0] if dir == 1
3016 // and [2] if dir == -1
3017 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 } else {
3019 SkScalar t = roots[0];
3020 SkScalar C = pts[0].fX;
3021 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3022 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003023 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003024 }
caryclark9aacd902015-12-14 08:38:09 -08003025 if (SkScalarNearlyEqual(xt, x)) {
3026 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3027 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003028 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003029 }
3030 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 return xt < x ? dir : 0;
3032}
3033
caryclark9aacd902015-12-14 08:38:09 -08003034static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003035 SkPoint dst[5];
3036 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003037
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003038 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3039 n = SkChopQuadAtYExtrema(pts, dst);
3040 pts = dst;
3041 }
caryclark9aacd902015-12-14 08:38:09 -08003042 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003043 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003044 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003045 }
3046 return w;
3047}
3048
caryclark9aacd902015-12-14 08:38:09 -08003049static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003050 SkScalar x0 = pts[0].fX;
3051 SkScalar y0 = pts[0].fY;
3052 SkScalar x1 = pts[1].fX;
3053 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003054
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003055 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003056
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003057 int dir = 1;
3058 if (y0 > y1) {
3059 SkTSwap(y0, y1);
3060 dir = -1;
3061 }
caryclark9aacd902015-12-14 08:38:09 -08003062 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003063 return 0;
3064 }
caryclark9cb5d752015-12-18 04:35:24 -08003065 if (checkOnCurve(x, y, pts[0], pts[1])) {
3066 *onCurveCount += 1;
3067 return 0;
3068 }
3069 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003070 return 0;
3071 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003072 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003073
caryclark9aacd902015-12-14 08:38:09 -08003074 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003075 // zero cross means the point is on the line, and since the case where
3076 // y of the query point is at the end point is handled above, we can be
3077 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003078 if (x != x1 || y != pts[1].fY) {
3079 *onCurveCount += 1;
3080 }
caryclark9aacd902015-12-14 08:38:09 -08003081 dir = 0;
3082 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003083 dir = 0;
3084 }
3085 return dir;
3086}
3087
caryclark9aacd902015-12-14 08:38:09 -08003088static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3089 SkTDArray<SkVector>* tangents) {
3090 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3091 && !between(pts[2].fY, y, pts[3].fY)) {
3092 return;
3093 }
3094 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3095 && !between(pts[2].fX, x, pts[3].fX)) {
3096 return;
3097 }
3098 SkPoint dst[10];
3099 int n = SkChopCubicAtYExtrema(pts, dst);
3100 for (int i = 0; i <= n; ++i) {
3101 SkPoint* c = &dst[i * 3];
3102 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003103 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003104 continue;
mbarbella276e6332016-05-31 14:44:01 -07003105 }
caryclark9aacd902015-12-14 08:38:09 -08003106 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3107 if (!SkScalarNearlyEqual(x, xt)) {
3108 continue;
3109 }
3110 SkVector tangent;
3111 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3112 tangents->push(tangent);
3113 }
3114}
3115
3116static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3117 SkTDArray<SkVector>* tangents) {
3118 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3119 return;
3120 }
3121 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3122 return;
3123 }
3124 SkScalar roots[2];
3125 SkScalar A = pts[2].fY;
3126 SkScalar B = pts[1].fY * w - y * w + y;
3127 SkScalar C = pts[0].fY;
3128 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3129 B -= C; // B = b*w - w * yCept + yCept - a
3130 C -= y;
3131 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3132 for (int index = 0; index < n; ++index) {
3133 SkScalar t = roots[index];
3134 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3135 if (!SkScalarNearlyEqual(x, xt)) {
3136 continue;
3137 }
3138 SkConic conic(pts, w);
3139 tangents->push(conic.evalTangentAt(t));
3140 }
3141}
3142
3143static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3144 SkTDArray<SkVector>* tangents) {
3145 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3146 return;
3147 }
3148 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3149 return;
3150 }
3151 SkScalar roots[2];
3152 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3153 2 * (pts[1].fY - pts[0].fY),
3154 pts[0].fY - y,
3155 roots);
3156 for (int index = 0; index < n; ++index) {
3157 SkScalar t = roots[index];
3158 SkScalar C = pts[0].fX;
3159 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3160 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003161 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003162 if (!SkScalarNearlyEqual(x, xt)) {
3163 continue;
3164 }
3165 tangents->push(SkEvalQuadTangentAt(pts, t));
3166 }
3167}
3168
3169static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3170 SkTDArray<SkVector>* tangents) {
3171 SkScalar y0 = pts[0].fY;
3172 SkScalar y1 = pts[1].fY;
3173 if (!between(y0, y, y1)) {
3174 return;
3175 }
3176 SkScalar x0 = pts[0].fX;
3177 SkScalar x1 = pts[1].fX;
3178 if (!between(x0, x, x1)) {
3179 return;
3180 }
3181 SkScalar dx = x1 - x0;
3182 SkScalar dy = y1 - y0;
3183 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3184 return;
3185 }
3186 SkVector v;
3187 v.set(dx, dy);
3188 tangents->push(v);
3189}
3190
reed@google.com4db592c2013-10-30 17:39:43 +00003191static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3192 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3193}
3194
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003195bool SkPath::contains(SkScalar x, SkScalar y) const {
3196 bool isInverse = this->isInverseFillType();
3197 if (this->isEmpty()) {
3198 return isInverse;
3199 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003200
reed@google.com4db592c2013-10-30 17:39:43 +00003201 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003202 return isInverse;
3203 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003204
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003205 SkPath::Iter iter(*this, true);
3206 bool done = false;
3207 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003208 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003209 do {
3210 SkPoint pts[4];
3211 switch (iter.next(pts, false)) {
3212 case SkPath::kMove_Verb:
3213 case SkPath::kClose_Verb:
3214 break;
3215 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003216 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003217 break;
3218 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003219 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003220 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003221 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003222 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003223 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003224 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003225 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003226 break;
3227 case SkPath::kDone_Verb:
3228 done = true;
3229 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003230 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003231 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003232 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3233 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3234 if (evenOddFill) {
3235 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003236 }
caryclark9aacd902015-12-14 08:38:09 -08003237 if (w) {
3238 return !isInverse;
3239 }
3240 if (onCurveCount <= 1) {
3241 return SkToBool(onCurveCount) ^ isInverse;
3242 }
3243 if ((onCurveCount & 1) || evenOddFill) {
3244 return SkToBool(onCurveCount & 1) ^ isInverse;
3245 }
halcanary9d524f22016-03-29 09:03:52 -07003246 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003247 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3248 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003249 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003250 SkTDArray<SkVector> tangents;
3251 do {
3252 SkPoint pts[4];
3253 int oldCount = tangents.count();
3254 switch (iter.next(pts, false)) {
3255 case SkPath::kMove_Verb:
3256 case SkPath::kClose_Verb:
3257 break;
3258 case SkPath::kLine_Verb:
3259 tangent_line(pts, x, y, &tangents);
3260 break;
3261 case SkPath::kQuad_Verb:
3262 tangent_quad(pts, x, y, &tangents);
3263 break;
3264 case SkPath::kConic_Verb:
3265 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3266 break;
3267 case SkPath::kCubic_Verb:
3268 tangent_cubic(pts, x, y, &tangents);
3269 break;
3270 case SkPath::kDone_Verb:
3271 done = true;
3272 break;
3273 }
3274 if (tangents.count() > oldCount) {
3275 int last = tangents.count() - 1;
3276 const SkVector& tangent = tangents[last];
3277 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3278 tangents.remove(last);
3279 } else {
3280 for (int index = 0; index < last; ++index) {
3281 const SkVector& test = tangents[index];
3282 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003283 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3284 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003285 tangents.remove(last);
3286 tangents.removeShuffle(index);
3287 break;
3288 }
3289 }
3290 }
3291 }
3292 } while (!done);
3293 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003294}
fmalitaaa0df4e2015-12-01 09:13:23 -08003295
3296int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3297 SkScalar w, SkPoint pts[], int pow2) {
3298 const SkConic conic(p0, p1, p2, w);
3299 return conic.chopIntoQuadsPOW2(pts, pow2);
3300}
bsalomonedc743a2016-06-01 09:42:31 -07003301
3302bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3303 unsigned* start) {
3304 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3305 return false;
3306 }
3307 SkPath::RawIter iter(path);
3308 SkPoint verbPts[4];
3309 SkPath::Verb v;
3310 SkPoint rectPts[5];
3311 int rectPtCnt = 0;
3312 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3313 switch (v) {
3314 case SkPath::kMove_Verb:
3315 if (0 != rectPtCnt) {
3316 return false;
3317 }
3318 rectPts[0] = verbPts[0];
3319 ++rectPtCnt;
3320 break;
3321 case SkPath::kLine_Verb:
3322 if (5 == rectPtCnt) {
3323 return false;
3324 }
3325 rectPts[rectPtCnt] = verbPts[1];
3326 ++rectPtCnt;
3327 break;
3328 case SkPath::kClose_Verb:
3329 if (4 == rectPtCnt) {
3330 rectPts[4] = rectPts[0];
3331 rectPtCnt = 5;
3332 }
3333 break;
3334 default:
3335 return false;
3336 }
3337 }
3338 if (rectPtCnt < 5) {
3339 return false;
3340 }
3341 if (rectPts[0] != rectPts[4]) {
3342 return false;
3343 }
bsalomon057ae8a2016-07-24 05:37:26 -07003344 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3345 // and pts 1 and 2 the opposite vertical or horizontal edge).
3346 bool vec03IsVertical;
3347 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3348 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3349 // Make sure it has non-zero width and height
3350 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003351 return false;
3352 }
bsalomon057ae8a2016-07-24 05:37:26 -07003353 vec03IsVertical = true;
3354 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3355 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3356 // Make sure it has non-zero width and height
3357 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3358 return false;
3359 }
3360 vec03IsVertical = false;
3361 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003362 return false;
3363 }
bsalomon057ae8a2016-07-24 05:37:26 -07003364 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3365 // set if it is on the bottom edge.
3366 unsigned sortFlags =
3367 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3368 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3369 switch (sortFlags) {
3370 case 0b00:
3371 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3372 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3373 *start = 0;
3374 break;
3375 case 0b01:
3376 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3377 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3378 *start = 1;
3379 break;
3380 case 0b10:
3381 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3382 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3383 *start = 3;
3384 break;
3385 case 0b11:
3386 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3387 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3388 *start = 2;
3389 break;
bsalomonedc743a2016-06-01 09:42:31 -07003390 }
bsalomonedc743a2016-06-01 09:42:31 -07003391 return true;
3392}
bsalomon21af9ca2016-08-25 12:29:23 -07003393
3394void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3395 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3396 SkASSERT(!oval.isEmpty());
3397 SkASSERT(sweepAngle);
3398
3399 path->reset();
3400 path->setIsVolatile(true);
3401 path->setFillType(SkPath::kWinding_FillType);
3402 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3403 path->addOval(oval);
3404 return;
3405 }
3406 if (useCenter) {
3407 path->moveTo(oval.centerX(), oval.centerY());
3408 }
3409 // Arc to mods at 360 and drawArc is not supposed to.
3410 bool forceMoveTo = !useCenter;
3411 while (sweepAngle <= -360.f) {
3412 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3413 startAngle -= 180.f;
3414 path->arcTo(oval, startAngle, -180.f, false);
3415 startAngle -= 180.f;
3416 forceMoveTo = false;
3417 sweepAngle += 360.f;
3418 }
3419 while (sweepAngle >= 360.f) {
3420 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3421 startAngle += 180.f;
3422 path->arcTo(oval, startAngle, 180.f, false);
3423 startAngle += 180.f;
3424 forceMoveTo = false;
3425 sweepAngle -= 360.f;
3426 }
3427 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3428 if (useCenter) {
3429 path->close();
3430 }
3431}
Mike Reed0d7dac82017-02-02 17:45:56 -08003432
3433///////////////////////////////////////////////////////////////////////////////////////////////////
3434#include "SkNx.h"
3435
3436static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3437 SkScalar ts[2];
3438 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3439 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3440 SkASSERT(n >= 0 && n <= 2);
3441 for (int i = 0; i < n; ++i) {
3442 extremas[i] = SkEvalQuadAt(src, ts[i]);
3443 }
3444 extremas[n] = src[2];
3445 return n + 1;
3446}
3447
3448static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3449 SkConic conic(src[0], src[1], src[2], w);
3450 SkScalar ts[2];
3451 int n = conic.findXExtrema(ts);
3452 n += conic.findYExtrema(&ts[n]);
3453 SkASSERT(n >= 0 && n <= 2);
3454 for (int i = 0; i < n; ++i) {
3455 extremas[i] = conic.evalAt(ts[i]);
3456 }
3457 extremas[n] = src[2];
3458 return n + 1;
3459}
3460
3461static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3462 SkScalar ts[4];
3463 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3464 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3465 SkASSERT(n >= 0 && n <= 4);
3466 for (int i = 0; i < n; ++i) {
3467 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3468 }
3469 extremas[n] = src[3];
3470 return n + 1;
3471}
3472
Mike Reed8d3196b2017-02-03 11:34:13 -05003473SkRect SkPath::computeTightBounds() const {
3474 if (0 == this->countVerbs()) {
3475 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003476 }
3477
Mike Reed8d3196b2017-02-03 11:34:13 -05003478 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3479 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003480 }
3481
3482 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3483 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003484 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003485
3486 // initial with the first MoveTo, so we don't have to check inside the switch
3487 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003488 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003489 for (;;) {
3490 int count = 0;
3491 switch (iter.next(pts)) {
3492 case SkPath::kMove_Verb:
3493 extremas[0] = pts[0];
3494 count = 1;
3495 break;
3496 case SkPath::kLine_Verb:
3497 extremas[0] = pts[1];
3498 count = 1;
3499 break;
3500 case SkPath::kQuad_Verb:
3501 count = compute_quad_extremas(pts, extremas);
3502 break;
3503 case SkPath::kConic_Verb:
3504 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3505 break;
3506 case SkPath::kCubic_Verb:
3507 count = compute_cubic_extremas(pts, extremas);
3508 break;
3509 case SkPath::kClose_Verb:
3510 break;
3511 case SkPath::kDone_Verb:
3512 goto DONE;
3513 }
3514 for (int i = 0; i < count; ++i) {
3515 Sk2s tmp = from_point(extremas[i]);
3516 min = Sk2s::Min(min, tmp);
3517 max = Sk2s::Max(max, tmp);
3518 }
3519 }
3520DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003521 SkRect bounds;
3522 min.store((SkPoint*)&bounds.fLeft);
3523 max.store((SkPoint*)&bounds.fRight);
3524 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003525}