blob: e304b4d8c5f5c8cf2b8f564c3df2da587458e7a8 [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];
278 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000279 SkDEBUGCODE(int segmentCount = 0;)
280 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);
293 SkDEBUGCODE(++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);
298 SkDEBUGCODE(++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);
303 SkDEBUGCODE(++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
335 return check_edge_against_rect(prevPt, firstPt, rect, direction);
336}
337
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000338uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000339 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800340#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000341 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
342 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
343#endif
344 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000345}
djsollen@google.come63793a2012-03-21 15:39:03 +0000346
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347void SkPath::reset() {
348 SkDEBUGCODE(this->validate();)
349
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000350 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000351 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352}
353
354void SkPath::rewind() {
355 SkDEBUGCODE(this->validate();)
356
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000357 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000358 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359}
360
fsb1475b02016-01-20 09:51:07 -0800361bool SkPath::isLastContourClosed() const {
362 int verbCount = fPathRef->countVerbs();
363 if (0 == verbCount) {
364 return false;
365 }
366 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
367}
368
reed@google.com7e6c4d12012-05-10 14:05:43 +0000369bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000371
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000372 if (2 == verbCount) {
373 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
374 if (kLine_Verb == fPathRef->atVerb(1)) {
375 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000376 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000377 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000378 line[0] = pts[0];
379 line[1] = pts[1];
380 }
381 return true;
382 }
383 }
384 return false;
385}
386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387/*
388 Determines if path is a rect by keeping track of changes in direction
389 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000390
caryclark@google.comf1316942011-07-26 19:54:45 +0000391 The direction is computed such that:
392 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000393 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000395 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000396
caryclark@google.comf1316942011-07-26 19:54:45 +0000397A rectangle cycles up/right/down/left or up/left/down/right.
398
399The test fails if:
400 The path is closed, and followed by a line.
401 A second move creates a new endpoint.
402 A diagonal line is parsed.
403 There's more than four changes of direction.
404 There's a discontinuity on the line (e.g., a move in the middle)
405 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 The path contains a quadratic or cubic.
407 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000408 *The rectangle doesn't complete a cycle.
409 *The final point isn't equal to the first point.
410
411 *These last two conditions we relax if we have a 3-edge path that would
412 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000413
caryclark@google.comf1316942011-07-26 19:54:45 +0000414It's OK if the path has:
415 Several colinear line segments composing a rectangle side.
416 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000417
caryclark@google.comf1316942011-07-26 19:54:45 +0000418The direction takes advantage of the corners found since opposite sides
419must travel in opposite directions.
420
421FIXME: Allow colinear quads and cubics to be treated like lines.
422FIXME: If the API passes fill-only, return true if the filled stroke
423 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424
425 first,last,next direction state-machine:
426 0x1 is set if the segment is horizontal
427 0x2 is set if the segment is moving to the right or down
428 thus:
429 two directions are opposites iff (dirA ^ dirB) == 0x2
430 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000431
caryclark@google.comf1316942011-07-26 19:54:45 +0000432 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000433static int rect_make_dir(SkScalar dx, SkScalar dy) {
434 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
435}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000436bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
437 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000438 int corners = 0;
439 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000440 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700441 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000442 first.set(0, 0);
443 last.set(0, 0);
444 int firstDirection = 0;
445 int lastDirection = 0;
446 int nextDirection = 0;
447 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700449 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000450 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000451 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700452 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
453 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000454 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000455 savePts = pts;
456 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700458 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 case kLine_Verb: {
460 SkScalar left = last.fX;
461 SkScalar top = last.fY;
462 SkScalar right = pts->fX;
463 SkScalar bottom = pts->fY;
464 ++pts;
465 if (left != right && top != bottom) {
466 return false; // diagonal
467 }
468 if (left == right && top == bottom) {
469 break; // single point on side OK
470 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000471 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 if (0 == corners) {
473 firstDirection = nextDirection;
474 first = last;
475 last = pts[-1];
476 corners = 1;
477 closedOrMoved = false;
478 break;
479 }
480 if (closedOrMoved) {
481 return false; // closed followed by a line
482 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000483 if (autoClose && nextDirection == firstDirection) {
484 break; // colinear with first
485 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000486 closedOrMoved = autoClose;
487 if (lastDirection != nextDirection) {
488 if (++corners > 4) {
489 return false; // too many direction changes
490 }
491 }
492 last = pts[-1];
493 if (lastDirection == nextDirection) {
494 break; // colinear segment
495 }
496 // Possible values for corners are 2, 3, and 4.
497 // When corners == 3, nextDirection opposes firstDirection.
498 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000499 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
501 if ((directionCycle ^ turn) != nextDirection) {
502 return false; // direction didn't follow cycle
503 }
504 break;
505 }
506 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000507 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 case kCubic_Verb:
509 return false; // quadratic, cubic not allowed
510 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700511 if (allowPartial && !autoClose && firstDirection) {
512 insertClose = true;
513 *currVerb -= 1; // try move again afterwards
514 goto addMissingClose;
515 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 last = *pts++;
517 closedOrMoved = true;
518 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000519 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000520 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000521 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000522 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000523 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000524 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700525addMissingClose:
526 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 }
528 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000529 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000530 if (!result) {
531 // check if we are just an incomplete rectangle, in which case we can
532 // return true, but not claim to be closed.
533 // e.g.
534 // 3 sided rectangle
535 // 4 sided but the last edge is not long enough to reach the start
536 //
537 SkScalar closeX = first.x() - last.x();
538 SkScalar closeY = first.y() - last.y();
539 if (closeX && closeY) {
540 return false; // we're diagonal, abort (can we ever reach this?)
541 }
542 int closeDirection = rect_make_dir(closeX, closeY);
543 // make sure the close-segment doesn't double-back on itself
544 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
545 result = true;
546 autoClose = false; // we are not closed
547 }
548 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000549 if (savePts) {
550 *ptsPtr = savePts;
551 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000552 if (result && isClosed) {
553 *isClosed = autoClose;
554 }
555 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000556 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000557 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 return result;
559}
560
robertphillips4f662e62014-12-29 14:06:51 -0800561bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000562 SkDEBUGCODE(this->validate();)
563 int currVerb = 0;
564 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800565 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800566 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800567 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000568 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800569 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800570 int32_t num = SkToS32(pts - first);
571 if (num) {
572 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800573 } else {
574 // 'pts' isn't updated for open rects
575 *rect = this->getBounds();
576 }
577 }
578 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000579}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000580
caryclark95bc5f32015-04-08 08:34:15 -0700581bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000582 SkDEBUGCODE(this->validate();)
583 int currVerb = 0;
584 const SkPoint* pts = fPathRef->points();
585 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000586 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700587 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 return false;
589 }
590 const SkPoint* last = pts;
591 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700592 bool isClosed;
593 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000594 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700595 if (!isClosed) {
596 pts = fPathRef->points() + fPathRef->countPoints();
597 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000598 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 if (testRects[0].contains(testRects[1])) {
600 if (rects) {
601 rects[0] = testRects[0];
602 rects[1] = testRects[1];
603 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000604 if (dirs) {
605 dirs[0] = testDirs[0];
606 dirs[1] = testDirs[1];
607 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 return true;
609 }
610 if (testRects[1].contains(testRects[0])) {
611 if (rects) {
612 rects[0] = testRects[1];
613 rects[1] = testRects[0];
614 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000615 if (dirs) {
616 dirs[0] = testDirs[1];
617 dirs[1] = testDirs[0];
618 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000619 return true;
620 }
621 }
622 return false;
623}
624
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625int SkPath::countPoints() const {
626 return fPathRef->countPoints();
627}
628
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000629int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 SkDEBUGCODE(this->validate();)
631
632 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000633 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800635 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000636 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637}
638
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000639SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
641 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000642 }
643 return SkPoint::Make(0, 0);
644}
645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646int SkPath::countVerbs() const {
647 return fPathRef->countVerbs();
648}
649
650static inline void copy_verbs_reverse(uint8_t* inorderDst,
651 const uint8_t* reversedSrc,
652 int count) {
653 for (int i = 0; i < count; ++i) {
654 inorderDst[i] = reversedSrc[~i];
655 }
656}
657
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000658int SkPath::getVerbs(uint8_t dst[], int max) const {
659 SkDEBUGCODE(this->validate();)
660
661 SkASSERT(max >= 0);
662 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 int count = SkMin32(max, fPathRef->countVerbs());
664 copy_verbs_reverse(dst, fPathRef->verbs(), count);
665 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000666}
667
reed@google.com294dd7b2011-10-11 11:58:32 +0000668bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 SkDEBUGCODE(this->validate();)
670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000672 if (count > 0) {
673 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000674 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000676 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000678 if (lastPt) {
679 lastPt->set(0, 0);
680 }
681 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682}
683
caryclarkaec25102015-04-29 08:28:30 -0700684void SkPath::setPt(int index, SkScalar x, SkScalar y) {
685 SkDEBUGCODE(this->validate();)
686
687 int count = fPathRef->countPoints();
688 if (count <= index) {
689 return;
690 } else {
691 SkPathRef::Editor ed(&fPathRef);
692 ed.atPoint(index)->set(x, y);
693 }
694}
695
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696void SkPath::setLastPt(SkScalar x, SkScalar y) {
697 SkDEBUGCODE(this->validate();)
698
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000699 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700 if (count == 0) {
701 this->moveTo(x, y);
702 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000703 SkPathRef::Editor ed(&fPathRef);
704 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 }
706}
707
reed@google.com04863fa2011-05-15 04:08:24 +0000708void SkPath::setConvexity(Convexity c) {
709 if (fConvexity != c) {
710 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000711 }
712}
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714//////////////////////////////////////////////////////////////////////////////
715// Construction methods
716
reed026beb52015-06-10 14:23:15 -0700717#define DIRTY_AFTER_EDIT \
718 do { \
719 fConvexity = kUnknown_Convexity; \
720 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000721 } while (0)
722
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723void SkPath::incReserve(U16CPU inc) {
724 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 SkDEBUGCODE(this->validate();)
727}
728
729void SkPath::moveTo(SkScalar x, SkScalar y) {
730 SkDEBUGCODE(this->validate();)
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000734 // remember our index
735 fLastMoveToIndex = fPathRef->countPoints();
736
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000737 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700738
739 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740}
741
742void SkPath::rMoveTo(SkScalar x, SkScalar y) {
743 SkPoint pt;
744 this->getLastPt(&pt);
745 this->moveTo(pt.fX + x, pt.fY + y);
746}
747
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000748void SkPath::injectMoveToIfNeeded() {
749 if (fLastMoveToIndex < 0) {
750 SkScalar x, y;
751 if (fPathRef->countVerbs() == 0) {
752 x = y = 0;
753 } else {
754 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
755 x = pt.fX;
756 y = pt.fY;
757 }
758 this->moveTo(x, y);
759 }
760}
761
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762void SkPath::lineTo(SkScalar x, SkScalar y) {
763 SkDEBUGCODE(this->validate();)
764
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000765 this->injectMoveToIfNeeded();
766
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000767 SkPathRef::Editor ed(&fPathRef);
768 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769
reed@google.comb54455e2011-05-16 14:16:04 +0000770 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771}
772
773void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000774 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775 SkPoint pt;
776 this->getLastPt(&pt);
777 this->lineTo(pt.fX + x, pt.fY + y);
778}
779
780void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
781 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 pts[0].set(x1, y1);
788 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000789
reed@google.comb54455e2011-05-16 14:16:04 +0000790 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791}
792
793void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000794 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 SkPoint pt;
796 this->getLastPt(&pt);
797 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
798}
799
reed@google.com277c3f82013-05-31 15:17:50 +0000800void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
801 SkScalar w) {
802 // check for <= 0 or NaN with this test
803 if (!(w > 0)) {
804 this->lineTo(x2, y2);
805 } else if (!SkScalarIsFinite(w)) {
806 this->lineTo(x1, y1);
807 this->lineTo(x2, y2);
808 } else if (SK_Scalar1 == w) {
809 this->quadTo(x1, y1, x2, y2);
810 } else {
811 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000812
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000813 this->injectMoveToIfNeeded();
814
reed@google.com277c3f82013-05-31 15:17:50 +0000815 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000816 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000817 pts[0].set(x1, y1);
818 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000819
reed@google.com277c3f82013-05-31 15:17:50 +0000820 DIRTY_AFTER_EDIT;
821 }
822}
823
824void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
825 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000826 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000827 SkPoint pt;
828 this->getLastPt(&pt);
829 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
830}
831
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
833 SkScalar x3, SkScalar y3) {
834 SkDEBUGCODE(this->validate();)
835
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000836 this->injectMoveToIfNeeded();
837
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000838 SkPathRef::Editor ed(&fPathRef);
839 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 pts[0].set(x1, y1);
841 pts[1].set(x2, y2);
842 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843
reed@google.comb54455e2011-05-16 14:16:04 +0000844 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845}
846
847void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
848 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000849 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 SkPoint pt;
851 this->getLastPt(&pt);
852 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
853 pt.fX + x3, pt.fY + y3);
854}
855
856void SkPath::close() {
857 SkDEBUGCODE(this->validate();)
858
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000859 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 case kLine_Verb:
863 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000864 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 case kMove_Verb: {
867 SkPathRef::Editor ed(&fPathRef);
868 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000870 }
reed@google.com277c3f82013-05-31 15:17:50 +0000871 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000872 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000873 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000874 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000875 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000876 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 }
878 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000879
880 // signal that we need a moveTo to follow us (unless we're done)
881#if 0
882 if (fLastMoveToIndex >= 0) {
883 fLastMoveToIndex = ~fLastMoveToIndex;
884 }
885#else
886 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
887#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888}
889
890///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000891
fmalitac08d53e2015-11-17 09:53:29 -0800892namespace {
893
894template <unsigned N>
895class PointIterator {
896public:
897 PointIterator(SkPath::Direction dir, unsigned startIndex)
898 : fCurrent(startIndex % N)
899 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
900
901 const SkPoint& current() const {
902 SkASSERT(fCurrent < N);
903 return fPts[fCurrent];
904 }
905
906 const SkPoint& next() {
907 fCurrent = (fCurrent + fAdvance) % N;
908 return this->current();
909 }
910
911protected:
912 SkPoint fPts[N];
913
914private:
915 unsigned fCurrent;
916 unsigned fAdvance;
917};
918
919class RectPointIterator : public PointIterator<4> {
920public:
921 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
922 : PointIterator(dir, startIndex) {
923
924 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
925 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
926 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
927 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
928 }
929};
930
931class OvalPointIterator : public PointIterator<4> {
932public:
933 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
934 : PointIterator(dir, startIndex) {
935
936 const SkScalar cx = oval.centerX();
937 const SkScalar cy = oval.centerY();
938
939 fPts[0] = SkPoint::Make(cx, oval.fTop);
940 fPts[1] = SkPoint::Make(oval.fRight, cy);
941 fPts[2] = SkPoint::Make(cx, oval.fBottom);
942 fPts[3] = SkPoint::Make(oval.fLeft, cy);
943 }
944};
945
946class RRectPointIterator : public PointIterator<8> {
947public:
948 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
949 : PointIterator(dir, startIndex) {
950
951 const SkRect& bounds = rrect.getBounds();
952 const SkScalar L = bounds.fLeft;
953 const SkScalar T = bounds.fTop;
954 const SkScalar R = bounds.fRight;
955 const SkScalar B = bounds.fBottom;
956
957 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
958 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
959 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
960 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
961 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
962 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
963 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
964 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
965 }
966};
967
968} // anonymous namespace
969
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000970static void assert_known_direction(int dir) {
971 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
972}
973
reed@android.com8a1c16f2008-12-17 15:59:43 +0000974void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800975 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976}
977
978void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
979 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800980 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
981}
982
983void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000984 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700985 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800986 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000987 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800988 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000989
fmalitac08d53e2015-11-17 09:53:29 -0800990 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991
fmalitac08d53e2015-11-17 09:53:29 -0800992 const int kVerbs = 5; // moveTo + 3x lineTo + close
993 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994
fmalitac08d53e2015-11-17 09:53:29 -0800995 RectPointIterator iter(rect, dir, startIndex);
996
997 this->moveTo(iter.current());
998 this->lineTo(iter.next());
999 this->lineTo(iter.next());
1000 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001002
1003 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004}
1005
reed@google.com744faba2012-05-29 19:54:52 +00001006void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1007 SkDEBUGCODE(this->validate();)
1008 if (count <= 0) {
1009 return;
1010 }
1011
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001012 fLastMoveToIndex = fPathRef->countPoints();
1013
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001014 // +close makes room for the extra kClose_Verb
1015 SkPathRef::Editor ed(&fPathRef, count+close, count);
1016
1017 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001018 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001019 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1020 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001021 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001022
reed@google.com744faba2012-05-29 19:54:52 +00001023 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001024 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001025 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001026 }
1027
reed@google.com744faba2012-05-29 19:54:52 +00001028 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001029 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001030}
1031
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001032#include "SkGeometry.h"
1033
reedf90ea012015-01-29 12:03:58 -08001034static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1035 SkPoint* pt) {
1036 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001037 // Chrome uses this path to move into and out of ovals. If not
1038 // treated as a special case the moves can distort the oval's
1039 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001040 pt->set(oval.fRight, oval.centerY());
1041 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042 } else if (0 == oval.width() && 0 == oval.height()) {
1043 // Chrome will sometimes create 0 radius round rects. Having degenerate
1044 // quad segments in the path prevents the path from being recognized as
1045 // a rect.
1046 // TODO: optimizing the case where only one of width or height is zero
1047 // should also be considered. This case, however, doesn't seem to be
1048 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001049 pt->set(oval.fRight, oval.fTop);
1050 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001051 }
reedf90ea012015-01-29 12:03:58 -08001052 return false;
1053}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001054
reedd5d27d92015-02-09 13:54:43 -08001055// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1056//
1057static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1058 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1059 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1060 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061
1062 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001063 loss in radians-conversion and/or sin/cos, we may end up with coincident
1064 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1065 of drawing a nearly complete circle (good).
1066 e.g. canvas.drawArc(0, 359.99, ...)
1067 -vs- canvas.drawArc(0, 359.9, ...)
1068 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069 */
reedd5d27d92015-02-09 13:54:43 -08001070 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071 SkScalar sw = SkScalarAbs(sweepAngle);
1072 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1073 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1074 // make a guess at a tiny angle (in radians) to tweak by
1075 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1076 // not sure how much will be enough, so we use a loop
1077 do {
1078 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001079 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1080 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081 }
1082 }
reedd5d27d92015-02-09 13:54:43 -08001083 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1084}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001085
reed9e779d42015-02-17 11:43:14 -08001086/**
1087 * If this returns 0, then the caller should just line-to the singlePt, else it should
1088 * ignore singlePt and append the specified number of conics.
1089 */
reedd5d27d92015-02-09 13:54:43 -08001090static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001091 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1092 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001093 SkMatrix matrix;
1094
1095 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1096 matrix.postTranslate(oval.centerX(), oval.centerY());
1097
reed9e779d42015-02-17 11:43:14 -08001098 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1099 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001100 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001101 }
1102 return count;
reedd5d27d92015-02-09 13:54:43 -08001103}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001104
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001105void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001107 SkRRect rrect;
1108 rrect.setRectRadii(rect, (const SkVector*) radii);
1109 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001110}
1111
reed@google.com4ed0fb72012-12-12 20:48:18 +00001112void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001113 // legacy start indices: 6 (CW) and 7(CCW)
1114 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1115}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001116
fmalitac08d53e2015-11-17 09:53:29 -08001117void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1118 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119
fmalitac08d53e2015-11-17 09:53:29 -08001120 if (rrect.isEmpty()) {
1121 return;
reed1b28a3a2014-12-17 14:42:09 -08001122 }
fmalitac08d53e2015-11-17 09:53:29 -08001123
caryclarkda707bf2015-11-19 14:47:43 -08001124 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001125 const SkRect& bounds = rrect.getBounds();
1126
1127 if (rrect.isRect()) {
1128 // degenerate(rect) => radii points are collapsing
1129 this->addRect(bounds, dir, (startIndex + 1) / 2);
1130 } else if (rrect.isOval()) {
1131 // degenerate(oval) => line points are collapsing
1132 this->addOval(bounds, dir, startIndex / 2);
1133 } else {
1134 fFirstDirection = this->hasOnlyMoveTos() ?
1135 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1136
1137 SkAutoPathBoundsUpdate apbu(this, bounds);
1138 SkAutoDisableDirectionCheck addc(this);
1139
1140 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1141 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1142 const SkScalar weight = SK_ScalarRoot2Over2;
1143
1144 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1145 const int kVerbs = startsWithConic
1146 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1147 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1148 this->incReserve(kVerbs);
1149
1150 RRectPointIterator rrectIter(rrect, dir, startIndex);
1151 // Corner iterator indices follow the collapsed radii model,
1152 // adjusted such that the start pt is "behind" the radii start pt.
1153 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1154 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1155
1156 this->moveTo(rrectIter.current());
1157 if (startsWithConic) {
1158 for (unsigned i = 0; i < 3; ++i) {
1159 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1160 this->lineTo(rrectIter.next());
1161 }
1162 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1163 // final lineTo handled by close().
1164 } else {
1165 for (unsigned i = 0; i < 4; ++i) {
1166 this->lineTo(rrectIter.next());
1167 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1168 }
1169 }
1170 this->close();
1171
caryclarkda707bf2015-11-19 14:47:43 -08001172 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001173 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001174
fmalitac08d53e2015-11-17 09:53:29 -08001175 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1176 }
1177
1178 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001179}
1180
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001182 int count = fPathRef->countVerbs();
1183 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1184 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001185 if (*verbs == kLine_Verb ||
1186 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001187 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001188 *verbs == kCubic_Verb) {
1189 return false;
1190 }
1191 ++verbs;
1192 }
1193 return true;
1194}
1195
Brian Osmana2318572017-07-10 15:09:26 -04001196bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1197 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001198 if (count < 2) {
1199 return true;
1200 }
Brian Osmana2318572017-07-10 15:09:26 -04001201 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001202 const SkPoint& first = *pts;
1203 for (int index = 1; index < count; ++index) {
1204 if (first != pts[index]) {
1205 return false;
1206 }
1207 }
1208 return true;
1209}
1210
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001211void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1212 Direction dir) {
1213 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001214
humper@google.com75e3ca12013-04-08 21:44:11 +00001215 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001216 return;
1217 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001218
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001219 SkRRect rrect;
1220 rrect.setRectXY(rect, rx, ry);
1221 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001222}
1223
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001225 // legacy start index: 1
1226 this->addOval(oval, dir, 1);
1227}
1228
1229void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001230 assert_known_direction(dir);
1231
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001232 /* If addOval() is called after previous moveTo(),
1233 this path is still marked as an oval. This is used to
1234 fit into WebKit's calling sequences.
1235 We can't simply check isEmpty() in this case, as additional
1236 moveTo() would mark the path non empty.
1237 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001238 bool isOval = hasOnlyMoveTos();
1239 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001240 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001241 } else {
reed026beb52015-06-10 14:23:15 -07001242 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001243 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001244
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001245 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 SkAutoPathBoundsUpdate apbu(this, oval);
1247
fmalitac08d53e2015-11-17 09:53:29 -08001248 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1249 const int kVerbs = 6; // moveTo + 4x conicTo + close
1250 this->incReserve(kVerbs);
1251
1252 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1253 // The corner iterator pts are tracking "behind" the oval/radii pts.
1254 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001255 const SkScalar weight = SK_ScalarRoot2Over2;
1256
fmalitac08d53e2015-11-17 09:53:29 -08001257 this->moveTo(ovalIter.current());
1258 for (unsigned i = 0; i < 4; ++i) {
1259 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001260 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262
fmalitac08d53e2015-11-17 09:53:29 -08001263 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1264
robertphillips@google.com466310d2013-12-03 16:43:54 +00001265 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001266
bsalomon78d58d12016-05-27 09:17:04 -07001267 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001268}
1269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1271 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001272 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 }
1274}
1275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1277 bool forceMoveTo) {
1278 if (oval.width() < 0 || oval.height() < 0) {
1279 return;
1280 }
1281
reedf90ea012015-01-29 12:03:58 -08001282 if (fPathRef->countVerbs() == 0) {
1283 forceMoveTo = true;
1284 }
1285
1286 SkPoint lonePt;
1287 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1288 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1289 return;
1290 }
1291
reedd5d27d92015-02-09 13:54:43 -08001292 SkVector startV, stopV;
1293 SkRotationDirection dir;
1294 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1295
reed9e779d42015-02-17 11:43:14 -08001296 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001297
1298 // At this point, we know that the arc is not a lone point, but startV == stopV
1299 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1300 // cannot handle it.
1301 if (startV == stopV) {
1302 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1303 SkScalar radiusX = oval.width() / 2;
1304 SkScalar radiusY = oval.height() / 2;
1305 // We cannot use SkScalarSinCos function in the next line because
1306 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1307 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001308 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001309 // make sin(endAngle) to be 0 which will then draw a dot.
1310 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1311 oval.centerY() + radiusY * sk_float_sin(endAngle));
1312 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1313 return;
1314 }
1315
reedd5d27d92015-02-09 13:54:43 -08001316 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001317 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001318 if (count) {
1319 this->incReserve(count * 2 + 1);
1320 const SkPoint& pt = conics[0].fPts[0];
1321 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1322 for (int i = 0; i < count; ++i) {
1323 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1324 }
reed9e779d42015-02-17 11:43:14 -08001325 } else {
1326 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001327 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328}
1329
caryclark55d49052016-01-23 05:07:04 -08001330// This converts the SVG arc to conics.
1331// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1332// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1333// See also SVG implementation notes:
1334// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1335// Note that arcSweep bool value is flipped from the original implementation.
1336void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1337 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001338 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001339 SkPoint srcPts[2];
1340 this->getLastPt(&srcPts[0]);
1341 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1342 // joining the endpoints.
1343 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1344 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001345 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001346 return;
1347 }
1348 // If the current point and target point for the arc are identical, it should be treated as a
1349 // zero length path. This ensures continuity in animations.
1350 srcPts[1].set(x, y);
1351 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001352 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001353 return;
1354 }
1355 rx = SkScalarAbs(rx);
1356 ry = SkScalarAbs(ry);
1357 SkVector midPointDistance = srcPts[0] - srcPts[1];
1358 midPointDistance *= 0.5f;
1359
1360 SkMatrix pointTransform;
1361 pointTransform.setRotate(-angle);
1362
1363 SkPoint transformedMidPoint;
1364 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1365 SkScalar squareRx = rx * rx;
1366 SkScalar squareRy = ry * ry;
1367 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1368 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1369
1370 // Check if the radii are big enough to draw the arc, scale radii if not.
1371 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1372 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1373 if (radiiScale > 1) {
1374 radiiScale = SkScalarSqrt(radiiScale);
1375 rx *= radiiScale;
1376 ry *= radiiScale;
1377 }
1378
1379 pointTransform.setScale(1 / rx, 1 / ry);
1380 pointTransform.preRotate(-angle);
1381
1382 SkPoint unitPts[2];
1383 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1384 SkVector delta = unitPts[1] - unitPts[0];
1385
1386 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1387 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1388
1389 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1390 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1391 scaleFactor = -scaleFactor;
1392 }
1393 delta.scale(scaleFactor);
1394 SkPoint centerPoint = unitPts[0] + unitPts[1];
1395 centerPoint *= 0.5f;
1396 centerPoint.offset(-delta.fY, delta.fX);
1397 unitPts[0] -= centerPoint;
1398 unitPts[1] -= centerPoint;
1399 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1400 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1401 SkScalar thetaArc = theta2 - theta1;
1402 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1403 thetaArc += SK_ScalarPI * 2;
1404 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1405 thetaArc -= SK_ScalarPI * 2;
1406 }
1407 pointTransform.setRotate(angle);
1408 pointTransform.preScale(rx, ry);
1409
1410 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1411 SkScalar thetaWidth = thetaArc / segments;
1412 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1413 if (!SkScalarIsFinite(t)) {
1414 return;
1415 }
1416 SkScalar startTheta = theta1;
1417 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1418 for (int i = 0; i < segments; ++i) {
1419 SkScalar endTheta = startTheta + thetaWidth;
1420 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1421
1422 unitPts[1].set(cosEndTheta, sinEndTheta);
1423 unitPts[1] += centerPoint;
1424 unitPts[0] = unitPts[1];
1425 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1426 SkPoint mapped[2];
1427 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1428 this->conicTo(mapped[0], mapped[1], w);
1429 startTheta = endTheta;
1430 }
1431}
1432
1433void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1434 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1435 SkPoint currentPoint;
1436 this->getLastPt(&currentPoint);
1437 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1438}
1439
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001440void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 if (oval.isEmpty() || 0 == sweepAngle) {
1442 return;
1443 }
1444
1445 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1446
1447 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001448 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1449 // See SkPath::addOval() docs.
1450 SkScalar startOver90 = startAngle / 90.f;
1451 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1452 SkScalar error = startOver90 - startOver90I;
1453 if (SkScalarNearlyEqual(error, 0)) {
1454 // Index 1 is at startAngle == 0.
1455 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1456 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1457 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1458 (unsigned) startIndex);
1459 return;
1460 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 }
bsalomon1978ce22016-05-31 10:42:16 -07001462 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463}
1464
1465/*
1466 Need to handle the case when the angle is sharp, and our computed end-points
1467 for the arc go behind pt1 and/or p2...
1468*/
reedc7789042015-01-29 12:59:11 -08001469void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001470 if (radius == 0) {
1471 this->lineTo(x1, y1);
1472 return;
1473 }
1474
1475 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001476
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477 // need to know our prev pt so we can construct tangent vectors
1478 {
1479 SkPoint start;
1480 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001481 // Handle degenerate cases by adding a line to the first point and
1482 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 before.setNormalize(x1 - start.fX, y1 - start.fY);
1484 after.setNormalize(x2 - x1, y2 - y1);
1485 }
reed@google.comabf15c12011-01-18 20:35:51 +00001486
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 SkScalar cosh = SkPoint::DotProduct(before, after);
1488 SkScalar sinh = SkPoint::CrossProduct(before, after);
1489
1490 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001491 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 return;
1493 }
reed@google.comabf15c12011-01-18 20:35:51 +00001494
Mike Reeda99b6ce2017-02-04 11:04:26 -05001495 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496
Mike Reeda99b6ce2017-02-04 11:04:26 -05001497 SkScalar xx = x1 - dist * before.fX;
1498 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001499 after.setLength(dist);
1500 this->lineTo(xx, yy);
1501 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1502 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503}
1504
1505///////////////////////////////////////////////////////////////////////////////
1506
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001507void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 SkMatrix matrix;
1509
1510 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001511 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512}
1513
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001514void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001515 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001517 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 SkPoint pts[4];
1519 Verb verb;
1520
1521 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001522 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 while ((verb = iter.next(pts)) != kDone_Verb) {
1524 switch (verb) {
1525 case kMove_Verb:
1526 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001527 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1528 injectMoveToIfNeeded(); // In case last contour is closed
1529 this->lineTo(pts[0]);
1530 } else {
1531 this->moveTo(pts[0]);
1532 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 break;
1534 case kLine_Verb:
1535 proc(matrix, &pts[1], &pts[1], 1);
1536 this->lineTo(pts[1]);
1537 break;
1538 case kQuad_Verb:
1539 proc(matrix, &pts[1], &pts[1], 2);
1540 this->quadTo(pts[1], pts[2]);
1541 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001542 case kConic_Verb:
1543 proc(matrix, &pts[1], &pts[1], 2);
1544 this->conicTo(pts[1], pts[2], iter.conicWeight());
1545 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 case kCubic_Verb:
1547 proc(matrix, &pts[1], &pts[1], 3);
1548 this->cubicTo(pts[1], pts[2], pts[3]);
1549 break;
1550 case kClose_Verb:
1551 this->close();
1552 break;
1553 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001554 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001556 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 }
1558}
1559
1560///////////////////////////////////////////////////////////////////////////////
1561
reed@google.com277c3f82013-05-31 15:17:50 +00001562static int pts_in_verb(unsigned verb) {
1563 static const uint8_t gPtsInVerb[] = {
1564 1, // kMove
1565 1, // kLine
1566 2, // kQuad
1567 2, // kConic
1568 3, // kCubic
1569 0, // kClose
1570 0 // kDone
1571 };
1572
1573 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1574 return gPtsInVerb[verb];
1575}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577// ignore the last point of the 1st contour
1578void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001579 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1580 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 return;
1582 }
caryclark51c56782016-11-07 05:09:28 -08001583 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1584 SkASSERT(verbsEnd[0] == kMove_Verb);
1585 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1586 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587
caryclark51c56782016-11-07 05:09:28 -08001588 while (verbs < verbsEnd) {
1589 uint8_t v = *verbs++;
1590 pts -= pts_in_verb(v);
1591 switch (v) {
1592 case kMove_Verb:
1593 // if the path has multiple contours, stop after reversing the last
1594 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001596 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 break;
1598 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001599 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001601 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001602 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001603 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001605 this->cubicTo(pts[2], pts[1], pts[0]);
1606 break;
1607 case kClose_Verb:
1608 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 break;
1610 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001611 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 break;
1613 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614 }
1615}
1616
reed@google.com63d73742012-01-10 15:33:12 +00001617void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001618 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001619
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001620 const SkPoint* pts = src.fPathRef->pointsEnd();
1621 // we will iterator through src's verbs backwards
1622 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1623 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001624 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001625
1626 bool needMove = true;
1627 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001628 while (verbs < verbsEnd) {
1629 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001630 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001631
1632 if (needMove) {
1633 --pts;
1634 this->moveTo(pts->fX, pts->fY);
1635 needMove = false;
1636 }
1637 pts -= n;
1638 switch (v) {
1639 case kMove_Verb:
1640 if (needClose) {
1641 this->close();
1642 needClose = false;
1643 }
1644 needMove = true;
1645 pts += 1; // so we see the point in "if (needMove)" above
1646 break;
1647 case kLine_Verb:
1648 this->lineTo(pts[0]);
1649 break;
1650 case kQuad_Verb:
1651 this->quadTo(pts[1], pts[0]);
1652 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001653 case kConic_Verb:
1654 this->conicTo(pts[1], pts[0], *--conicWeights);
1655 break;
reed@google.com63d73742012-01-10 15:33:12 +00001656 case kCubic_Verb:
1657 this->cubicTo(pts[2], pts[1], pts[0]);
1658 break;
1659 case kClose_Verb:
1660 needClose = true;
1661 break;
1662 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001663 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001664 }
1665 }
1666}
1667
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668///////////////////////////////////////////////////////////////////////////////
1669
1670void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1671 SkMatrix matrix;
1672
1673 matrix.setTranslate(dx, dy);
1674 this->transform(matrix, dst);
1675}
1676
reed@android.com8a1c16f2008-12-17 15:59:43 +00001677static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1678 int level = 2) {
1679 if (--level >= 0) {
1680 SkPoint tmp[7];
1681
1682 SkChopCubicAtHalf(pts, tmp);
1683 subdivide_cubic_to(path, &tmp[0], level);
1684 subdivide_cubic_to(path, &tmp[3], level);
1685 } else {
1686 path->cubicTo(pts[1], pts[2], pts[3]);
1687 }
1688}
1689
1690void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1691 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001692 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693 dst = (SkPath*)this;
1694 }
1695
tomhudson@google.com8d430182011-06-06 19:11:19 +00001696 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 SkPath tmp;
1698 tmp.fFillType = fFillType;
1699
1700 SkPath::Iter iter(*this, false);
1701 SkPoint pts[4];
1702 SkPath::Verb verb;
1703
reed@google.com4a3b7142012-05-16 17:16:46 +00001704 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 switch (verb) {
1706 case kMove_Verb:
1707 tmp.moveTo(pts[0]);
1708 break;
1709 case kLine_Verb:
1710 tmp.lineTo(pts[1]);
1711 break;
1712 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001713 // promote the quad to a conic
1714 tmp.conicTo(pts[1], pts[2],
1715 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001717 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001718 tmp.conicTo(pts[1], pts[2],
1719 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001720 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 case kCubic_Verb:
1722 subdivide_cubic_to(&tmp, pts);
1723 break;
1724 case kClose_Verb:
1725 tmp.close();
1726 break;
1727 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001728 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 break;
1730 }
1731 }
1732
1733 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001734 SkPathRef::Editor ed(&dst->fPathRef);
1735 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001736 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001738 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1739
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001742 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001743 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001745
reed026beb52015-06-10 14:23:15 -07001746 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1747 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001748 } else {
1749 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001750 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1751 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001752 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001753 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1754 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001755 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001756 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001757 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001758 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001759 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001760 }
1761 }
1762
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 SkDEBUGCODE(dst->validate();)
1764 }
1765}
1766
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767///////////////////////////////////////////////////////////////////////////////
1768///////////////////////////////////////////////////////////////////////////////
1769
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001770enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001771 kEmptyContour_SegmentState, // The current contour is empty. We may be
1772 // starting processing or we may have just
1773 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1775 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1776 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777};
1778
1779SkPath::Iter::Iter() {
1780#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001781 fPts = nullptr;
1782 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001784 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001785 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786#endif
1787 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001788 fVerbs = nullptr;
1789 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 fNeedClose = false;
1791}
1792
1793SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1794 this->setPath(path, forceClose);
1795}
1796
1797void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001798 fPts = path.fPathRef->points();
1799 fVerbs = path.fPathRef->verbs();
1800 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001801 fConicWeights = path.fPathRef->conicWeights();
1802 if (fConicWeights) {
1803 fConicWeights -= 1; // begin one behind
1804 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001805 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001806 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 fForceClose = SkToU8(forceClose);
1808 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001809 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810}
1811
1812bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001813 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 return false;
1815 }
1816 if (fForceClose) {
1817 return true;
1818 }
1819
1820 const uint8_t* verbs = fVerbs;
1821 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001822
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001823 if (kMove_Verb == *(verbs - 1)) {
1824 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 }
1826
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 while (verbs > stop) {
1828 // verbs points one beyond the current verb, decrement first.
1829 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 if (kMove_Verb == v) {
1831 break;
1832 }
1833 if (kClose_Verb == v) {
1834 return true;
1835 }
1836 }
1837 return false;
1838}
1839
1840SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001841 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001843 // A special case: if both points are NaN, SkPoint::operation== returns
1844 // false, but the iterator expects that they are treated as the same.
1845 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001846 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1847 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001848 return kClose_Verb;
1849 }
1850
reed@google.com9e25dbf2012-05-15 17:05:38 +00001851 pts[0] = fLastPt;
1852 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853 fLastPt = fMoveTo;
1854 fCloseLine = true;
1855 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001856 } else {
1857 pts[0] = fMoveTo;
1858 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860}
1861
reed@google.com9e25dbf2012-05-15 17:05:38 +00001862const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 if (fSegmentState == kAfterMove_SegmentState) {
1864 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001866 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1869 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001870 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872}
1873
caryclarke8c56662015-07-14 11:19:26 -07001874void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 // We need to step over anything that will not move the current draw point
1876 // forward before the next move is seen
1877 const uint8_t* lastMoveVerb = 0;
1878 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001879 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 SkPoint lastPt = fLastPt;
1881 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 switch (verb) {
1884 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 // Keep a record of this most recent move
1886 lastMoveVerb = fVerbs;
1887 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001888 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001889 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001890 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 fPts++;
1892 break;
1893
1894 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001895 // A close when we are in a segment is always valid except when it
1896 // follows a move which follows a segment.
1897 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 return;
1899 }
1900 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001901 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 break;
1903
1904 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001905 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 if (lastMoveVerb) {
1907 fVerbs = lastMoveVerb;
1908 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001909 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 return;
1911 }
1912 return;
1913 }
1914 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001915 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 fPts++;
1917 break;
1918
reed@google.com277c3f82013-05-31 15:17:50 +00001919 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001921 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 if (lastMoveVerb) {
1923 fVerbs = lastMoveVerb;
1924 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001925 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 return;
1927 }
1928 return;
1929 }
1930 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001933 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 break;
1935
1936 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001937 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 if (lastMoveVerb) {
1939 fVerbs = lastMoveVerb;
1940 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001941 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 return;
1943 }
1944 return;
1945 }
1946 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fPts += 3;
1949 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001950
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001952 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001953 }
1954 }
1955}
1956
reed@google.com4a3b7142012-05-16 17:16:46 +00001957SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001958 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 // Close the curve if requested and if there is some curve to close
1962 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001963 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 return kLine_Verb;
1965 }
1966 fNeedClose = false;
1967 return kClose_Verb;
1968 }
1969 return kDone_Verb;
1970 }
1971
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 // fVerbs is one beyond the current verb, decrement first
1973 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001974 const SkPoint* SK_RESTRICT srcPts = fPts;
1975 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976
1977 switch (verb) {
1978 case kMove_Verb:
1979 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001980 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 verb = this->autoClose(pts);
1982 if (verb == kClose_Verb) {
1983 fNeedClose = false;
1984 }
1985 return (Verb)verb;
1986 }
1987 if (fVerbs == fVerbStop) { // might be a trailing moveto
1988 return kDone_Verb;
1989 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001990 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001991 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001993 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 fNeedClose = fForceClose;
1996 break;
1997 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001998 pts[0] = this->cons_moveTo();
1999 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 fLastPt = srcPts[0];
2001 fCloseLine = false;
2002 srcPts += 1;
2003 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002004 case kConic_Verb:
2005 fConicWeights += 1;
2006 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002008 pts[0] = this->cons_moveTo();
2009 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 fLastPt = srcPts[1];
2011 srcPts += 2;
2012 break;
2013 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002014 pts[0] = this->cons_moveTo();
2015 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 fLastPt = srcPts[2];
2017 srcPts += 3;
2018 break;
2019 case kClose_Verb:
2020 verb = this->autoClose(pts);
2021 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023 } else {
2024 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002025 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002027 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 break;
2029 }
2030 fPts = srcPts;
2031 return (Verb)verb;
2032}
2033
2034///////////////////////////////////////////////////////////////////////////////
2035
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002037 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038*/
2039
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002040size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 SkDEBUGCODE(this->validate();)
2042
halcanary96fcdcc2015-08-27 07:41:13 -07002043 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002044 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002045 return SkAlign4(byteCount);
2046 }
2047
2048 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002049
robertphillips@google.com466310d2013-12-03 16:43:54 +00002050 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002051 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002052 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002053 (fIsVolatile << kIsVolatile_SerializationShift) |
2054 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002055
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002056 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002057 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002059 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002060
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002061 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002062 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063}
2064
Mike Reed41a930f2017-07-26 17:33:44 -04002065sk_sp<SkData> SkPath::serialize() const {
2066 size_t size = this->writeToMemory(nullptr);
2067 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2068 this->writeToMemory(data->writable_data());
2069 return data;
2070}
2071
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002072size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002073 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002074
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002075 int32_t packed;
2076 if (!buffer.readS32(&packed)) {
2077 return 0;
2078 }
2079
reed8f086022015-06-11 14:22:19 -07002080 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002081 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2082 return 0;
2083 }
mtklein1b249332015-07-07 12:21:21 -07002084
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002085 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002086 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002087 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002088 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002089 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002090 if (!pathRef) {
2091 return 0;
2092 }
2093
2094 fPathRef.reset(pathRef);
2095 SkDEBUGCODE(this->validate();)
2096 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002097
reed8f086022015-06-11 14:22:19 -07002098 // compatibility check
2099 if (version < kPathPrivFirstDirection_Version) {
2100 switch (dir) { // old values
2101 case 0:
2102 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2103 break;
2104 case 1:
2105 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2106 break;
2107 case 2:
2108 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2109 break;
2110 default:
2111 SkASSERT(false);
2112 }
2113 } else {
2114 fFirstDirection = dir;
2115 }
2116
ajumaf8aec582016-01-13 13:46:31 -08002117 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118}
2119
2120///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121
Ben Wagner4d1955c2017-03-10 13:08:15 -05002122#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002123#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002124#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002125
reed@google.com51bbe752013-01-17 22:07:50 +00002126static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002127 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002128 str->append(label);
2129 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002130
reed@google.com51bbe752013-01-17 22:07:50 +00002131 const SkScalar* values = &pts[0].fX;
2132 count *= 2;
2133
2134 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002135 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002136 if (i < count - 1) {
2137 str->append(", ");
2138 }
2139 }
reed@google.com277c3f82013-05-31 15:17:50 +00002140 if (conicWeight >= 0) {
2141 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002142 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002143 }
caryclark08fa28c2014-10-23 13:08:56 -07002144 str->append(");");
reede05fed02014-12-15 07:59:53 -08002145 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002146 str->append(" // ");
2147 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002148 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002149 if (i < count - 1) {
2150 str->append(", ");
2151 }
2152 }
2153 if (conicWeight >= 0) {
2154 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002155 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002156 }
2157 }
2158 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002159}
2160
caryclarke9562592014-09-15 09:26:09 -07002161void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002162 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 Iter iter(*this, forceClose);
2164 SkPoint pts[4];
2165 Verb verb;
2166
reed@google.com51bbe752013-01-17 22:07:50 +00002167 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002168 char const * const gFillTypeStrs[] = {
2169 "Winding",
2170 "EvenOdd",
2171 "InverseWinding",
2172 "InverseEvenOdd",
2173 };
2174 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2175 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002176 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177 switch (verb) {
2178 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002179 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 break;
2181 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002182 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002185 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002187 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002188 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002189 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002191 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192 break;
2193 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002194 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 break;
2196 default:
2197 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2198 verb = kDone_Verb; // stop the loop
2199 break;
2200 }
caryclark1049f122015-04-20 08:31:59 -07002201 if (!wStream && builder.size()) {
2202 SkDebugf("%s", builder.c_str());
2203 builder.reset();
2204 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002205 }
caryclark66a5d8b2014-06-24 08:30:15 -07002206 if (wStream) {
2207 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002208 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002209}
2210
reed@android.come522ca52009-11-23 20:10:41 +00002211void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002212 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002213}
2214
2215void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002216 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002217}
2218
2219#ifdef SK_DEBUG
2220void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002221 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002222
djsollen@google.com077348c2012-10-22 20:23:32 +00002223#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002224 if (!fBoundsIsDirty) {
2225 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002226
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002227 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002228 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002229
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002230 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002231 // if we're empty, fBounds may be empty but translated, so we can't
2232 // necessarily compare to bounds directly
2233 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2234 // be [2, 2, 2, 2]
2235 SkASSERT(bounds.isEmpty());
2236 SkASSERT(fBounds.isEmpty());
2237 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002238 if (bounds.isEmpty()) {
2239 SkASSERT(fBounds.isEmpty());
2240 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002241 if (!fBounds.isEmpty()) {
2242 SkASSERT(fBounds.contains(bounds));
2243 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002244 }
reed@android.come522ca52009-11-23 20:10:41 +00002245 }
2246 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002247#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002248}
djsollen@google.com077348c2012-10-22 20:23:32 +00002249#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002250
reed@google.com04863fa2011-05-15 04:08:24 +00002251///////////////////////////////////////////////////////////////////////////////
2252
reed@google.com0b7b9822011-05-16 12:29:27 +00002253static int sign(SkScalar x) { return x < 0; }
2254#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002255
robertphillipsc506e302014-09-16 09:43:31 -07002256enum DirChange {
2257 kLeft_DirChange,
2258 kRight_DirChange,
2259 kStraight_DirChange,
2260 kBackwards_DirChange,
2261
2262 kInvalid_DirChange
2263};
2264
2265
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002266static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002267 // The error epsilon was empirically derived; worse case round rects
2268 // with a mid point outset by 2x float epsilon in tests had an error
2269 // of 12.
2270 const int epsilon = 16;
2271 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2272 return false;
2273 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002274 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002275 int aBits = SkFloatAs2sCompliment(compA);
2276 int bBits = SkFloatAs2sCompliment(compB);
2277 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002278}
2279
caryclarkb4216502015-03-02 13:02:34 -08002280static bool approximately_zero_when_compared_to(double x, double y) {
2281 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002282}
2283
caryclarkb4216502015-03-02 13:02:34 -08002284
reed@google.com04863fa2011-05-15 04:08:24 +00002285// only valid for a single contour
2286struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002287 Convexicator()
2288 : fPtCount(0)
2289 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002290 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002291 , fIsFinite(true)
2292 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002293 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 // warnings
djsollen2f124632016-04-29 13:53:05 -07002295 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002296 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002297 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002298 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002299 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002300
2301 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002302 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002303 }
2304
2305 SkPath::Convexity getConvexity() const { return fConvexity; }
2306
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002307 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002308 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002309
reed@google.com04863fa2011-05-15 04:08:24 +00002310 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002311 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002312 return;
2313 }
2314
2315 if (0 == fPtCount) {
2316 fCurrPt = pt;
2317 ++fPtCount;
2318 } else {
2319 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002320 SkScalar lengthSqd = vec.lengthSqd();
2321 if (!SkScalarIsFinite(lengthSqd)) {
2322 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002323 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002324 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002325 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002326 fCurrPt = pt;
2327 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002328 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 } else {
2330 SkASSERT(fPtCount > 2);
2331 this->addVec(vec);
2332 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002333
reed@google.com85b6e392011-05-15 20:25:17 +00002334 int sx = sign(vec.fX);
2335 int sy = sign(vec.fY);
2336 fDx += (sx != fSx);
2337 fDy += (sy != fSy);
2338 fSx = sx;
2339 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002340
reed@google.com85b6e392011-05-15 20:25:17 +00002341 if (fDx > 3 || fDy > 3) {
2342 fConvexity = SkPath::kConcave_Convexity;
2343 }
reed@google.com04863fa2011-05-15 04:08:24 +00002344 }
2345 }
2346 }
2347
2348 void close() {
2349 if (fPtCount > 2) {
2350 this->addVec(fFirstVec);
2351 }
2352 }
2353
caryclarkb4216502015-03-02 13:02:34 -08002354 DirChange directionChange(const SkVector& curVec) {
2355 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2356
2357 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2358 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2359 largest = SkTMax(largest, -smallest);
2360
2361 if (!almost_equal(largest, largest + cross)) {
2362 int sign = SkScalarSignAsInt(cross);
2363 if (sign) {
2364 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2365 }
2366 }
2367
2368 if (cross) {
2369 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2370 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2371 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2372 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2373 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2374 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2375 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2376 if (sign) {
2377 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2378 }
2379 }
2380 }
2381
2382 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2383 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2384 fLastVec.dot(curVec) < 0.0f) {
2385 return kBackwards_DirChange;
2386 }
2387
2388 return kStraight_DirChange;
2389 }
2390
2391
caryclarkd3d1a982014-12-08 04:57:38 -08002392 bool isFinite() const {
2393 return fIsFinite;
2394 }
2395
caryclark5ccef572015-03-02 10:07:56 -08002396 void setCurve(bool isCurve) {
2397 fIsCurve = isCurve;
2398 }
2399
reed@google.com04863fa2011-05-15 04:08:24 +00002400private:
2401 void addVec(const SkVector& vec) {
2402 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002403 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002404 switch (dir) {
2405 case kLeft_DirChange: // fall through
2406 case kRight_DirChange:
2407 if (kInvalid_DirChange == fExpectedDir) {
2408 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002409 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2410 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002411 } else if (dir != fExpectedDir) {
2412 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002413 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002414 }
2415 fLastVec = vec;
2416 break;
2417 case kStraight_DirChange:
2418 break;
2419 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002420 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002421 // If any of the subsequent dir is non-backward, it'll be concave.
2422 // Otherwise, it's still convex.
2423 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002424 }
robertphillipsc506e302014-09-16 09:43:31 -07002425 fLastVec = vec;
2426 break;
2427 case kInvalid_DirChange:
2428 SkFAIL("Use of invalid direction change flag");
2429 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002430 }
2431 }
2432
caryclarkb4216502015-03-02 13:02:34 -08002433 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002434 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002435 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002436 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2437 // value with the current vec is deemed to be of a significant value.
2438 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002439 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002440 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002442 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002443 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002444 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002445 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002446};
2447
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448SkPath::Convexity SkPath::internalGetConvexity() const {
2449 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002450 SkPoint pts[4];
2451 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002452 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002453
2454 int contourCount = 0;
2455 int count;
2456 Convexicator state;
2457
caryclarkd3d1a982014-12-08 04:57:38 -08002458 if (!isFinite()) {
2459 return kUnknown_Convexity;
2460 }
caryclarke8c56662015-07-14 11:19:26 -07002461 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002462 switch (verb) {
2463 case kMove_Verb:
2464 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002465 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002466 return kConcave_Convexity;
2467 }
2468 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002469 // fall through
2470 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002471 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002472 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002473 break;
caryclark5ccef572015-03-02 10:07:56 -08002474 case kQuad_Verb:
2475 // fall through
2476 case kConic_Verb:
2477 // fall through
2478 case kCubic_Verb:
2479 count = 2 + (kCubic_Verb == verb);
2480 // As an additional enhancement, this could set curve true only
2481 // if the curve is nonlinear
2482 state.setCurve(true);
2483 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002484 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002485 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002486 state.close();
2487 count = 0;
2488 break;
2489 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002490 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002491 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002492 return kConcave_Convexity;
2493 }
2494
2495 for (int i = 1; i <= count; i++) {
2496 state.addPt(pts[i]);
2497 }
2498 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002499 if (!state.isFinite()) {
2500 return kUnknown_Convexity;
2501 }
reed@google.com04863fa2011-05-15 04:08:24 +00002502 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002503 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002504 return kConcave_Convexity;
2505 }
2506 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002507 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002508 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2509 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002510 }
2511 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002512}
reed@google.com69a99432012-01-10 18:00:10 +00002513
2514///////////////////////////////////////////////////////////////////////////////
2515
2516class ContourIter {
2517public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002518 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002519
2520 bool done() const { return fDone; }
2521 // if !done() then these may be called
2522 int count() const { return fCurrPtCount; }
2523 const SkPoint* pts() const { return fCurrPt; }
2524 void next();
2525
2526private:
2527 int fCurrPtCount;
2528 const SkPoint* fCurrPt;
2529 const uint8_t* fCurrVerb;
2530 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002531 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002532 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002533 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002534};
2535
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002536ContourIter::ContourIter(const SkPathRef& pathRef) {
2537 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002538 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002539 fCurrPt = pathRef.points();
2540 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002541 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002542 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002543 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002544 this->next();
2545}
2546
2547void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002548 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002549 fDone = true;
2550 }
2551 if (fDone) {
2552 return;
2553 }
2554
2555 // skip pts of prev contour
2556 fCurrPt += fCurrPtCount;
2557
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002558 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002559 int ptCount = 1; // moveTo
2560 const uint8_t* verbs = fCurrVerb;
2561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002562 for (--verbs; verbs > fStopVerbs; --verbs) {
2563 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002564 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002565 goto CONTOUR_END;
2566 case SkPath::kLine_Verb:
2567 ptCount += 1;
2568 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002569 case SkPath::kConic_Verb:
2570 fCurrConicWeight += 1;
2571 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002572 case SkPath::kQuad_Verb:
2573 ptCount += 2;
2574 break;
2575 case SkPath::kCubic_Verb:
2576 ptCount += 3;
2577 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002578 case SkPath::kClose_Verb:
2579 break;
2580 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002581 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002582 break;
2583 }
2584 }
2585CONTOUR_END:
2586 fCurrPtCount = ptCount;
2587 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002588 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002589}
2590
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002591// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002592static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002593 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2594 // We may get 0 when the above subtracts underflow. We expect this to be
2595 // very rare and lazily promote to double.
2596 if (0 == cross) {
2597 double p0x = SkScalarToDouble(p0.fX);
2598 double p0y = SkScalarToDouble(p0.fY);
2599
2600 double p1x = SkScalarToDouble(p1.fX);
2601 double p1y = SkScalarToDouble(p1.fY);
2602
2603 double p2x = SkScalarToDouble(p2.fX);
2604 double p2y = SkScalarToDouble(p2.fY);
2605
2606 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2607 (p1y - p0y) * (p2x - p0x));
2608
2609 }
2610 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002611}
2612
reed@google.comc1ea60a2012-01-31 15:15:36 +00002613// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002614static int find_max_y(const SkPoint pts[], int count) {
2615 SkASSERT(count > 0);
2616 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002617 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002618 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619 SkScalar y = pts[i].fY;
2620 if (y > max) {
2621 max = y;
2622 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002623 }
2624 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002625 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002626}
2627
reed@google.comcabaf1d2012-01-11 21:03:05 +00002628static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2629 int i = index;
2630 for (;;) {
2631 i = (i + inc) % n;
2632 if (i == index) { // we wrapped around, so abort
2633 break;
2634 }
2635 if (pts[index] != pts[i]) { // found a different point, success!
2636 break;
2637 }
2638 }
2639 return i;
2640}
2641
reed@google.comc1ea60a2012-01-31 15:15:36 +00002642/**
2643 * Starting at index, and moving forward (incrementing), find the xmin and
2644 * xmax of the contiguous points that have the same Y.
2645 */
2646static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2647 int* maxIndexPtr) {
2648 const SkScalar y = pts[index].fY;
2649 SkScalar min = pts[index].fX;
2650 SkScalar max = min;
2651 int minIndex = index;
2652 int maxIndex = index;
2653 for (int i = index + 1; i < n; ++i) {
2654 if (pts[i].fY != y) {
2655 break;
2656 }
2657 SkScalar x = pts[i].fX;
2658 if (x < min) {
2659 min = x;
2660 minIndex = i;
2661 } else if (x > max) {
2662 max = x;
2663 maxIndex = i;
2664 }
2665 }
2666 *maxIndexPtr = maxIndex;
2667 return minIndex;
2668}
2669
reed026beb52015-06-10 14:23:15 -07002670static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2671 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002672}
2673
reed@google.comac8543f2012-01-30 20:51:25 +00002674/*
2675 * We loop through all contours, and keep the computed cross-product of the
2676 * contour that contained the global y-max. If we just look at the first
2677 * contour, we may find one that is wound the opposite way (correctly) since
2678 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2679 * that is outer most (or at least has the global y-max) before we can consider
2680 * its cross product.
2681 */
reed026beb52015-06-10 14:23:15 -07002682bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002683 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2684 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002685 return true;
2686 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687
2688 // don't want to pay the cost for computing this if it
2689 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002690 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2691 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002692 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002693 return false;
2694 }
reed@google.com69a99432012-01-10 18:00:10 +00002695
reed026beb52015-06-10 14:23:15 -07002696 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002697
reed@google.comac8543f2012-01-30 20:51:25 +00002698 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002699 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002700 SkScalar ymaxCross = 0;
2701
reed@google.com69a99432012-01-10 18:00:10 +00002702 for (; !iter.done(); iter.next()) {
2703 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002704 if (n < 3) {
2705 continue;
2706 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002707
reed@google.comcabaf1d2012-01-11 21:03:05 +00002708 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002709 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002710 int index = find_max_y(pts, n);
2711 if (pts[index].fY < ymax) {
2712 continue;
2713 }
2714
2715 // If there is more than 1 distinct point at the y-max, we take the
2716 // x-min and x-max of them and just subtract to compute the dir.
2717 if (pts[(index + 1) % n].fY == pts[index].fY) {
2718 int maxIndex;
2719 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2720 if (minIndex == maxIndex) {
2721 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002722 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002723 SkASSERT(pts[minIndex].fY == pts[index].fY);
2724 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2725 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2726 // we just subtract the indices, and let that auto-convert to
2727 // SkScalar, since we just want - or + to signal the direction.
2728 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002729 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002730 TRY_CROSSPROD:
2731 // Find a next and prev index to use for the cross-product test,
2732 // but we try to find pts that form non-zero vectors from pts[index]
2733 //
2734 // Its possible that we can't find two non-degenerate vectors, so
2735 // we have to guard our search (e.g. all the pts could be in the
2736 // same place).
2737
2738 // we pass n - 1 instead of -1 so we don't foul up % operator by
2739 // passing it a negative LH argument.
2740 int prev = find_diff_pt(pts, index, n, n - 1);
2741 if (prev == index) {
2742 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002743 continue;
2744 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002745 int next = find_diff_pt(pts, index, n, 1);
2746 SkASSERT(next != index);
2747 cross = cross_prod(pts[prev], pts[index], pts[next]);
2748 // if we get a zero and the points are horizontal, then we look at the spread in
2749 // x-direction. We really should continue to walk away from the degeneracy until
2750 // there is a divergence.
2751 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2752 // construct the subtract so we get the correct Direction below
2753 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002754 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002755 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002756
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002757 if (cross) {
2758 // record our best guess so far
2759 ymax = pts[index].fY;
2760 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002761 }
2762 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002763 if (ymaxCross) {
2764 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002765 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002766 return true;
2767 } else {
2768 return false;
2769 }
reed@google.comac8543f2012-01-30 20:51:25 +00002770}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771
2772///////////////////////////////////////////////////////////////////////////////
2773
caryclark9aacd902015-12-14 08:38:09 -08002774static bool between(SkScalar a, SkScalar b, SkScalar c) {
2775 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2776 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2777 return (a - b) * (c - b) <= 0;
2778}
2779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2781 SkScalar t) {
2782 SkScalar A = c3 + 3*(c1 - c2) - c0;
2783 SkScalar B = 3*(c2 - c1 - c1 + c0);
2784 SkScalar C = 3*(c1 - c0);
2785 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002786 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787}
2788
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002789template <size_t N> static void find_minmax(const SkPoint pts[],
2790 SkScalar* minPtr, SkScalar* maxPtr) {
2791 SkScalar min, max;
2792 min = max = pts[0].fX;
2793 for (size_t i = 1; i < N; ++i) {
2794 min = SkMinScalar(min, pts[i].fX);
2795 max = SkMaxScalar(max, pts[i].fX);
2796 }
2797 *minPtr = min;
2798 *maxPtr = max;
2799}
2800
caryclark9cb5d752015-12-18 04:35:24 -08002801static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2802 if (start.fY == end.fY) {
2803 return between(start.fX, x, end.fX) && x != end.fX;
2804 } else {
2805 return x == start.fX && y == start.fY;
2806 }
2807}
2808
caryclark9aacd902015-12-14 08:38:09 -08002809static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002810 SkScalar y0 = pts[0].fY;
2811 SkScalar y3 = pts[3].fY;
2812
2813 int dir = 1;
2814 if (y0 > y3) {
2815 SkTSwap(y0, y3);
2816 dir = -1;
2817 }
2818 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002819 return 0;
2820 }
caryclark9cb5d752015-12-18 04:35:24 -08002821 if (checkOnCurve(x, y, pts[0], pts[3])) {
2822 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002823 return 0;
2824 }
caryclark9cb5d752015-12-18 04:35:24 -08002825 if (y == y3) {
2826 return 0;
2827 }
2828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 // quickreject or quickaccept
2830 SkScalar min, max;
2831 find_minmax<4>(pts, &min, &max);
2832 if (x < min) {
2833 return 0;
2834 }
2835 if (x > max) {
2836 return dir;
2837 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002838
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002840 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002841 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002842 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002843 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002844 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002845 if (SkScalarNearlyEqual(xt, x)) {
2846 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2847 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002848 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002849 }
2850 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851 return xt < x ? dir : 0;
2852}
2853
caryclark9aacd902015-12-14 08:38:09 -08002854static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 SkPoint dst[10];
2856 int n = SkChopCubicAtYExtrema(pts, dst);
2857 int w = 0;
2858 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002859 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 }
2861 return w;
2862}
2863
caryclark9aacd902015-12-14 08:38:09 -08002864static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2865 SkASSERT(src);
2866 SkASSERT(t >= 0 && t <= 1);
2867 SkScalar src2w = src[2] * w;
2868 SkScalar C = src[0];
2869 SkScalar A = src[4] - 2 * src2w + C;
2870 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002871 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002872}
2873
2874
2875static double conic_eval_denominator(SkScalar w, SkScalar t) {
2876 SkScalar B = 2 * (w - 1);
2877 SkScalar C = 1;
2878 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002879 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002880}
2881
2882static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2883 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 SkScalar y0 = pts[0].fY;
2885 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002886
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002887 int dir = 1;
2888 if (y0 > y2) {
2889 SkTSwap(y0, y2);
2890 dir = -1;
2891 }
caryclark9aacd902015-12-14 08:38:09 -08002892 if (y < y0 || y > y2) {
2893 return 0;
2894 }
caryclark9cb5d752015-12-18 04:35:24 -08002895 if (checkOnCurve(x, y, pts[0], pts[2])) {
2896 *onCurveCount += 1;
2897 return 0;
2898 }
caryclark9aacd902015-12-14 08:38:09 -08002899 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002900 return 0;
2901 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002902
caryclark9aacd902015-12-14 08:38:09 -08002903 SkScalar roots[2];
2904 SkScalar A = pts[2].fY;
2905 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2906 SkScalar C = pts[0].fY;
2907 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2908 B -= C; // B = b*w - w * yCept + yCept - a
2909 C -= y;
2910 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2911 SkASSERT(n <= 1);
2912 SkScalar xt;
2913 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002914 // zero roots are returned only when y0 == y
2915 // Need [0] if dir == 1
2916 // and [2] if dir == -1
2917 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002918 } else {
2919 SkScalar t = roots[0];
2920 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2921 }
2922 if (SkScalarNearlyEqual(xt, x)) {
2923 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2924 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002925 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002926 }
2927 }
2928 return xt < x ? dir : 0;
2929}
2930
2931static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2932 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2933 if (y0 == y1) {
2934 return true;
2935 }
2936 if (y0 < y1) {
2937 return y1 <= y2;
2938 } else {
2939 return y1 >= y2;
2940 }
2941}
2942
2943static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2944 int* onCurveCount) {
2945 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002946 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002947 // If the data points are very large, the conic may not be monotonic but may also
2948 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002949 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2950 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2951 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002952 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2953 }
2954 return w;
2955}
2956
2957static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2958 SkScalar y0 = pts[0].fY;
2959 SkScalar y2 = pts[2].fY;
2960
2961 int dir = 1;
2962 if (y0 > y2) {
2963 SkTSwap(y0, y2);
2964 dir = -1;
2965 }
2966 if (y < y0 || y > y2) {
2967 return 0;
2968 }
caryclark9cb5d752015-12-18 04:35:24 -08002969 if (checkOnCurve(x, y, pts[0], pts[2])) {
2970 *onCurveCount += 1;
2971 return 0;
2972 }
caryclark9aacd902015-12-14 08:38:09 -08002973 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002974 return 0;
2975 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 // bounds check on X (not required. is it faster?)
2977#if 0
2978 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2979 return 0;
2980 }
2981#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002982
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 SkScalar roots[2];
2984 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2985 2 * (pts[1].fY - pts[0].fY),
2986 pts[0].fY - y,
2987 roots);
2988 SkASSERT(n <= 1);
2989 SkScalar xt;
2990 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002991 // zero roots are returned only when y0 == y
2992 // Need [0] if dir == 1
2993 // and [2] if dir == -1
2994 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 } else {
2996 SkScalar t = roots[0];
2997 SkScalar C = pts[0].fX;
2998 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2999 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003000 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 }
caryclark9aacd902015-12-14 08:38:09 -08003002 if (SkScalarNearlyEqual(xt, x)) {
3003 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3004 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003005 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003006 }
3007 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 return xt < x ? dir : 0;
3009}
3010
caryclark9aacd902015-12-14 08:38:09 -08003011static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 SkPoint dst[5];
3013 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003014
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3016 n = SkChopQuadAtYExtrema(pts, dst);
3017 pts = dst;
3018 }
caryclark9aacd902015-12-14 08:38:09 -08003019 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003021 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 }
3023 return w;
3024}
3025
caryclark9aacd902015-12-14 08:38:09 -08003026static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 SkScalar x0 = pts[0].fX;
3028 SkScalar y0 = pts[0].fY;
3029 SkScalar x1 = pts[1].fX;
3030 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003031
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003032 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003033
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003034 int dir = 1;
3035 if (y0 > y1) {
3036 SkTSwap(y0, y1);
3037 dir = -1;
3038 }
caryclark9aacd902015-12-14 08:38:09 -08003039 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003040 return 0;
3041 }
caryclark9cb5d752015-12-18 04:35:24 -08003042 if (checkOnCurve(x, y, pts[0], pts[1])) {
3043 *onCurveCount += 1;
3044 return 0;
3045 }
3046 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003047 return 0;
3048 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003049 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003050
caryclark9aacd902015-12-14 08:38:09 -08003051 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003052 // zero cross means the point is on the line, and since the case where
3053 // y of the query point is at the end point is handled above, we can be
3054 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003055 if (x != x1 || y != pts[1].fY) {
3056 *onCurveCount += 1;
3057 }
caryclark9aacd902015-12-14 08:38:09 -08003058 dir = 0;
3059 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003060 dir = 0;
3061 }
3062 return dir;
3063}
3064
caryclark9aacd902015-12-14 08:38:09 -08003065static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3066 SkTDArray<SkVector>* tangents) {
3067 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3068 && !between(pts[2].fY, y, pts[3].fY)) {
3069 return;
3070 }
3071 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3072 && !between(pts[2].fX, x, pts[3].fX)) {
3073 return;
3074 }
3075 SkPoint dst[10];
3076 int n = SkChopCubicAtYExtrema(pts, dst);
3077 for (int i = 0; i <= n; ++i) {
3078 SkPoint* c = &dst[i * 3];
3079 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003080 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003081 continue;
mbarbella276e6332016-05-31 14:44:01 -07003082 }
caryclark9aacd902015-12-14 08:38:09 -08003083 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3084 if (!SkScalarNearlyEqual(x, xt)) {
3085 continue;
3086 }
3087 SkVector tangent;
3088 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3089 tangents->push(tangent);
3090 }
3091}
3092
3093static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3094 SkTDArray<SkVector>* tangents) {
3095 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3096 return;
3097 }
3098 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3099 return;
3100 }
3101 SkScalar roots[2];
3102 SkScalar A = pts[2].fY;
3103 SkScalar B = pts[1].fY * w - y * w + y;
3104 SkScalar C = pts[0].fY;
3105 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3106 B -= C; // B = b*w - w * yCept + yCept - a
3107 C -= y;
3108 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3109 for (int index = 0; index < n; ++index) {
3110 SkScalar t = roots[index];
3111 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3112 if (!SkScalarNearlyEqual(x, xt)) {
3113 continue;
3114 }
3115 SkConic conic(pts, w);
3116 tangents->push(conic.evalTangentAt(t));
3117 }
3118}
3119
3120static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3121 SkTDArray<SkVector>* tangents) {
3122 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3123 return;
3124 }
3125 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3126 return;
3127 }
3128 SkScalar roots[2];
3129 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3130 2 * (pts[1].fY - pts[0].fY),
3131 pts[0].fY - y,
3132 roots);
3133 for (int index = 0; index < n; ++index) {
3134 SkScalar t = roots[index];
3135 SkScalar C = pts[0].fX;
3136 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3137 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003138 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003139 if (!SkScalarNearlyEqual(x, xt)) {
3140 continue;
3141 }
3142 tangents->push(SkEvalQuadTangentAt(pts, t));
3143 }
3144}
3145
3146static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3147 SkTDArray<SkVector>* tangents) {
3148 SkScalar y0 = pts[0].fY;
3149 SkScalar y1 = pts[1].fY;
3150 if (!between(y0, y, y1)) {
3151 return;
3152 }
3153 SkScalar x0 = pts[0].fX;
3154 SkScalar x1 = pts[1].fX;
3155 if (!between(x0, x, x1)) {
3156 return;
3157 }
3158 SkScalar dx = x1 - x0;
3159 SkScalar dy = y1 - y0;
3160 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3161 return;
3162 }
3163 SkVector v;
3164 v.set(dx, dy);
3165 tangents->push(v);
3166}
3167
reed@google.com4db592c2013-10-30 17:39:43 +00003168static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3169 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3170}
3171
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172bool SkPath::contains(SkScalar x, SkScalar y) const {
3173 bool isInverse = this->isInverseFillType();
3174 if (this->isEmpty()) {
3175 return isInverse;
3176 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003177
reed@google.com4db592c2013-10-30 17:39:43 +00003178 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003179 return isInverse;
3180 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003181
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003182 SkPath::Iter iter(*this, true);
3183 bool done = false;
3184 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003185 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 do {
3187 SkPoint pts[4];
3188 switch (iter.next(pts, false)) {
3189 case SkPath::kMove_Verb:
3190 case SkPath::kClose_Verb:
3191 break;
3192 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003193 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 break;
3195 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003196 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003197 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003198 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003199 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003200 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003201 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003202 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003203 break;
3204 case SkPath::kDone_Verb:
3205 done = true;
3206 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003207 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003208 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003209 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3210 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3211 if (evenOddFill) {
3212 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003213 }
caryclark9aacd902015-12-14 08:38:09 -08003214 if (w) {
3215 return !isInverse;
3216 }
3217 if (onCurveCount <= 1) {
3218 return SkToBool(onCurveCount) ^ isInverse;
3219 }
3220 if ((onCurveCount & 1) || evenOddFill) {
3221 return SkToBool(onCurveCount & 1) ^ isInverse;
3222 }
halcanary9d524f22016-03-29 09:03:52 -07003223 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003224 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3225 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003226 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003227 SkTDArray<SkVector> tangents;
3228 do {
3229 SkPoint pts[4];
3230 int oldCount = tangents.count();
3231 switch (iter.next(pts, false)) {
3232 case SkPath::kMove_Verb:
3233 case SkPath::kClose_Verb:
3234 break;
3235 case SkPath::kLine_Verb:
3236 tangent_line(pts, x, y, &tangents);
3237 break;
3238 case SkPath::kQuad_Verb:
3239 tangent_quad(pts, x, y, &tangents);
3240 break;
3241 case SkPath::kConic_Verb:
3242 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3243 break;
3244 case SkPath::kCubic_Verb:
3245 tangent_cubic(pts, x, y, &tangents);
3246 break;
3247 case SkPath::kDone_Verb:
3248 done = true;
3249 break;
3250 }
3251 if (tangents.count() > oldCount) {
3252 int last = tangents.count() - 1;
3253 const SkVector& tangent = tangents[last];
3254 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3255 tangents.remove(last);
3256 } else {
3257 for (int index = 0; index < last; ++index) {
3258 const SkVector& test = tangents[index];
3259 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003260 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3261 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003262 tangents.remove(last);
3263 tangents.removeShuffle(index);
3264 break;
3265 }
3266 }
3267 }
3268 }
3269 } while (!done);
3270 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003271}
fmalitaaa0df4e2015-12-01 09:13:23 -08003272
3273int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3274 SkScalar w, SkPoint pts[], int pow2) {
3275 const SkConic conic(p0, p1, p2, w);
3276 return conic.chopIntoQuadsPOW2(pts, pow2);
3277}
bsalomonedc743a2016-06-01 09:42:31 -07003278
3279bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3280 unsigned* start) {
3281 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3282 return false;
3283 }
3284 SkPath::RawIter iter(path);
3285 SkPoint verbPts[4];
3286 SkPath::Verb v;
3287 SkPoint rectPts[5];
3288 int rectPtCnt = 0;
3289 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3290 switch (v) {
3291 case SkPath::kMove_Verb:
3292 if (0 != rectPtCnt) {
3293 return false;
3294 }
3295 rectPts[0] = verbPts[0];
3296 ++rectPtCnt;
3297 break;
3298 case SkPath::kLine_Verb:
3299 if (5 == rectPtCnt) {
3300 return false;
3301 }
3302 rectPts[rectPtCnt] = verbPts[1];
3303 ++rectPtCnt;
3304 break;
3305 case SkPath::kClose_Verb:
3306 if (4 == rectPtCnt) {
3307 rectPts[4] = rectPts[0];
3308 rectPtCnt = 5;
3309 }
3310 break;
3311 default:
3312 return false;
3313 }
3314 }
3315 if (rectPtCnt < 5) {
3316 return false;
3317 }
3318 if (rectPts[0] != rectPts[4]) {
3319 return false;
3320 }
bsalomon057ae8a2016-07-24 05:37:26 -07003321 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3322 // and pts 1 and 2 the opposite vertical or horizontal edge).
3323 bool vec03IsVertical;
3324 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3325 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3326 // Make sure it has non-zero width and height
3327 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003328 return false;
3329 }
bsalomon057ae8a2016-07-24 05:37:26 -07003330 vec03IsVertical = true;
3331 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3332 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3333 // Make sure it has non-zero width and height
3334 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3335 return false;
3336 }
3337 vec03IsVertical = false;
3338 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003339 return false;
3340 }
bsalomon057ae8a2016-07-24 05:37:26 -07003341 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3342 // set if it is on the bottom edge.
3343 unsigned sortFlags =
3344 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3345 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3346 switch (sortFlags) {
3347 case 0b00:
3348 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3349 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3350 *start = 0;
3351 break;
3352 case 0b01:
3353 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3354 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3355 *start = 1;
3356 break;
3357 case 0b10:
3358 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3359 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3360 *start = 3;
3361 break;
3362 case 0b11:
3363 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3364 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3365 *start = 2;
3366 break;
bsalomonedc743a2016-06-01 09:42:31 -07003367 }
bsalomonedc743a2016-06-01 09:42:31 -07003368 return true;
3369}
bsalomon21af9ca2016-08-25 12:29:23 -07003370
3371void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3372 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3373 SkASSERT(!oval.isEmpty());
3374 SkASSERT(sweepAngle);
3375
3376 path->reset();
3377 path->setIsVolatile(true);
3378 path->setFillType(SkPath::kWinding_FillType);
3379 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3380 path->addOval(oval);
3381 return;
3382 }
3383 if (useCenter) {
3384 path->moveTo(oval.centerX(), oval.centerY());
3385 }
3386 // Arc to mods at 360 and drawArc is not supposed to.
3387 bool forceMoveTo = !useCenter;
3388 while (sweepAngle <= -360.f) {
3389 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3390 startAngle -= 180.f;
3391 path->arcTo(oval, startAngle, -180.f, false);
3392 startAngle -= 180.f;
3393 forceMoveTo = false;
3394 sweepAngle += 360.f;
3395 }
3396 while (sweepAngle >= 360.f) {
3397 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3398 startAngle += 180.f;
3399 path->arcTo(oval, startAngle, 180.f, false);
3400 startAngle += 180.f;
3401 forceMoveTo = false;
3402 sweepAngle -= 360.f;
3403 }
3404 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3405 if (useCenter) {
3406 path->close();
3407 }
3408}
Mike Reed0d7dac82017-02-02 17:45:56 -08003409
3410///////////////////////////////////////////////////////////////////////////////////////////////////
3411#include "SkNx.h"
3412
3413static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3414 SkScalar ts[2];
3415 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3416 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3417 SkASSERT(n >= 0 && n <= 2);
3418 for (int i = 0; i < n; ++i) {
3419 extremas[i] = SkEvalQuadAt(src, ts[i]);
3420 }
3421 extremas[n] = src[2];
3422 return n + 1;
3423}
3424
3425static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3426 SkConic conic(src[0], src[1], src[2], w);
3427 SkScalar ts[2];
3428 int n = conic.findXExtrema(ts);
3429 n += conic.findYExtrema(&ts[n]);
3430 SkASSERT(n >= 0 && n <= 2);
3431 for (int i = 0; i < n; ++i) {
3432 extremas[i] = conic.evalAt(ts[i]);
3433 }
3434 extremas[n] = src[2];
3435 return n + 1;
3436}
3437
3438static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3439 SkScalar ts[4];
3440 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3441 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3442 SkASSERT(n >= 0 && n <= 4);
3443 for (int i = 0; i < n; ++i) {
3444 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3445 }
3446 extremas[n] = src[3];
3447 return n + 1;
3448}
3449
Mike Reed8d3196b2017-02-03 11:34:13 -05003450SkRect SkPath::computeTightBounds() const {
3451 if (0 == this->countVerbs()) {
3452 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003453 }
3454
Mike Reed8d3196b2017-02-03 11:34:13 -05003455 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3456 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003457 }
3458
3459 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3460 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003461 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003462
3463 // initial with the first MoveTo, so we don't have to check inside the switch
3464 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003465 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003466 for (;;) {
3467 int count = 0;
3468 switch (iter.next(pts)) {
3469 case SkPath::kMove_Verb:
3470 extremas[0] = pts[0];
3471 count = 1;
3472 break;
3473 case SkPath::kLine_Verb:
3474 extremas[0] = pts[1];
3475 count = 1;
3476 break;
3477 case SkPath::kQuad_Verb:
3478 count = compute_quad_extremas(pts, extremas);
3479 break;
3480 case SkPath::kConic_Verb:
3481 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3482 break;
3483 case SkPath::kCubic_Verb:
3484 count = compute_cubic_extremas(pts, extremas);
3485 break;
3486 case SkPath::kClose_Verb:
3487 break;
3488 case SkPath::kDone_Verb:
3489 goto DONE;
3490 }
3491 for (int i = 0; i < count; ++i) {
3492 Sk2s tmp = from_point(extremas[i]);
3493 min = Sk2s::Min(min, tmp);
3494 max = Sk2s::Max(max, tmp);
3495 }
3496 }
3497DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003498 SkRect bounds;
3499 min.store((SkPoint*)&bounds.fLeft);
3500 max.store((SkPoint*)&bounds.fRight);
3501 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003502}