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