blob: d80ceeefe22f01e0ca0a73c04457233771dc73f2 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Hal Canaryfdcfb8b2018-06-13 09:42:32 -04008#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"
Hal Canary22be4c42018-06-12 12:37:31 -040013#include "SkMacros.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040015#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070016#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000017#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050018#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000019#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050020#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021
Mike Reeda99b6ce2017-02-04 11:04:26 -050022static float poly_eval(float A, float B, float C, float t) {
23 return (A * t + B) * t + C;
24}
25
26static float poly_eval(float A, float B, float C, float D, float t) {
27 return ((A * t + B) * t + C) * t + D;
28}
29
reed@android.com8a1c16f2008-12-17 15:59:43 +000030////////////////////////////////////////////////////////////////////////////
31
reed@google.com3563c9e2011-11-14 19:34:57 +000032/**
33 * Path.bounds is defined to be the bounds of all the control points.
34 * If we called bounds.join(r) we would skip r if r was empty, which breaks
35 * our promise. Hence we have a custom joiner that doesn't look at emptiness
36 */
37static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
38 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
39 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
40 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
41 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
42}
43
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000044static bool is_degenerate(const SkPath& path) {
45 SkPath::Iter iter(path, false);
46 SkPoint pts[4];
47 return SkPath::kDone_Verb == iter.next(pts);
48}
49
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050class SkAutoDisableDirectionCheck {
51public:
52 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070053 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000054 }
55
56 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070057 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000058 }
59
60private:
reed026beb52015-06-10 14:23:15 -070061 SkPath* fPath;
62 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000063};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000064#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000065
reed@android.com8a1c16f2008-12-17 15:59:43 +000066/* This guy's constructor/destructor bracket a path editing operation. It is
67 used when we know the bounds of the amount we are going to add to the path
68 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000069
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000071 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000072 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000073
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000074 It also notes if the path was originally degenerate, and if so, sets
75 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000076 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 */
78class SkAutoPathBoundsUpdate {
79public:
80 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
81 this->init(path);
82 }
83
84 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
85 SkScalar right, SkScalar bottom) {
86 fRect.set(left, top, right, bottom);
87 this->init(path);
88 }
reed@google.comabf15c12011-01-18 20:35:51 +000089
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000091 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
92 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050093 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 }
96 }
reed@google.comabf15c12011-01-18 20:35:51 +000097
reed@android.com8a1c16f2008-12-17 15:59:43 +000098private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000099 SkPath* fPath;
100 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000102 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000103 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000104
reed@android.com6b82d1a2009-06-03 02:35:01 +0000105 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 // Cannot use fRect for our bounds unless we know it is sorted
107 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000109 // Mark the path's bounds as dirty if (1) they are, or (2) the path
110 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000111 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000113 if (fHasValidBounds && !fEmpty) {
114 joinNoEmptyChecks(&fRect, fPath->getBounds());
115 }
116 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 }
118};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000119#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121////////////////////////////////////////////////////////////////////////////
122
123/*
124 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000125 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
127
128 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000129 1. If we encounter degenerate segments, remove them
130 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
131 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
132 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133*/
134
135////////////////////////////////////////////////////////////////////////////
136
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000137// flag to require a moveTo if we begin with something else, like lineTo etc.
138#define INITIAL_LASTMOVETOINDEX_VALUE ~0
139
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000140SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800141 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000142 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700143 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400144 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145}
146
147void SkPath::resetFields() {
148 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000149 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000151 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700152 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000153
154 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700155 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000156}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157
bungeman@google.coma5809a32013-06-21 15:13:34 +0000158SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000159 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000160 this->copyFields(that);
161 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162}
163
164SkPath::~SkPath() {
165 SkDEBUGCODE(this->validate();)
166}
167
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168SkPath& SkPath::operator=(const SkPath& that) {
169 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170
bungeman@google.coma5809a32013-06-21 15:13:34 +0000171 if (this != &that) {
172 fPathRef.reset(SkRef(that.fPathRef.get()));
173 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 }
175 SkDEBUGCODE(this->validate();)
176 return *this;
177}
178
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179void SkPath::copyFields(const SkPath& that) {
180 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000181 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700183 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400184 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400185
186 // Non-atomic assignment of atomic values.
187 fConvexity .store(that.fConvexity .load());
188 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000189}
190
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000191bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000192 // note: don't need to look at isConvex or bounds, since just comparing the
193 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000194 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000195 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000196}
197
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700200 fPathRef.swap(that.fPathRef);
Ben Wagnercee46e52018-06-12 16:30:29 -0400201 SkTSwap(fLastMoveToIndex, that.fLastMoveToIndex);
202 SkTSwap(fFillType, that.fFillType);
203 SkTSwap(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400204
205 // Non-atomic swaps of atomic values.
206 Convexity c = fConvexity.load();
207 fConvexity.store(that.fConvexity.load());
208 that.fConvexity.store(c);
209
210 uint8_t fd = fFirstDirection.load();
211 fFirstDirection.store(that.fFirstDirection.load());
212 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 }
214}
215
caryclark8e7b19d2016-02-18 04:11:48 -0800216bool SkPath::isInterpolatable(const SkPath& compare) const {
217 int count = fPathRef->countVerbs();
218 if (count != compare.fPathRef->countVerbs()) {
219 return false;
220 }
221 if (!count) {
222 return true;
223 }
224 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
225 count)) {
226 return false;
227 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800228 return !fPathRef->countWeights() ||
229 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800230 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
231}
232
233bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500234 int pointCount = fPathRef->countPoints();
235 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800236 return false;
237 }
Cary Clark7487ec82018-03-06 09:30:46 -0500238 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800239 return true;
240 }
caryclark8e7b19d2016-02-18 04:11:48 -0800241 out->reset();
242 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700243 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800244 return true;
245}
246
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247static inline bool check_edge_against_rect(const SkPoint& p0,
248 const SkPoint& p1,
249 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700250 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000251 const SkPoint* edgeBegin;
252 SkVector v;
reed026beb52015-06-10 14:23:15 -0700253 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 v = p1 - p0;
255 edgeBegin = &p0;
256 } else {
257 v = p0 - p1;
258 edgeBegin = &p1;
259 }
260 if (v.fX || v.fY) {
261 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500262 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
263 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
264 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
265 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
267 return false;
268 }
269 }
270 return true;
271}
272
273bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
274 // This only handles non-degenerate convex paths currently.
275 if (kConvex_Convexity != this->getConvexity()) {
276 return false;
277 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000278
reed026beb52015-06-10 14:23:15 -0700279 SkPathPriv::FirstDirection direction;
280 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281 return false;
282 }
283
284 SkPoint firstPt;
285 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500286 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 SkPath::Verb verb;
288 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400289 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000291 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000292
Lee Salzmana19f0242017-01-12 13:06:21 -0500293 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 int nextPt = -1;
295 switch (verb) {
296 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 SkDEBUGCODE(++moveCnt);
299 firstPt = prevPt = pts[0];
300 break;
301 case kLine_Verb:
302 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400304 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 break;
306 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000307 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000308 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400309 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000310 nextPt = 2;
311 break;
312 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000313 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400314 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315 nextPt = 3;
316 break;
317 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000318 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000319 break;
320 default:
321 SkDEBUGFAIL("unknown verb");
322 }
323 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800324 if (SkPath::kConic_Verb == verb) {
325 SkConic orig;
326 orig.set(pts, iter.conicWeight());
327 SkPoint quadPts[5];
328 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800329 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800330
331 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
332 return false;
333 }
334 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
335 return false;
336 }
337 } else {
338 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
339 return false;
340 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000341 }
342 prevPt = pts[nextPt];
343 }
344 }
345
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400346 if (segmentCount) {
347 return check_edge_against_rect(prevPt, firstPt, rect, direction);
348 }
349 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000350}
351
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000352uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000353 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800354#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400355 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
356 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000357#endif
358 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000359}
djsollen@google.come63793a2012-03-21 15:39:03 +0000360
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361void SkPath::reset() {
362 SkDEBUGCODE(this->validate();)
363
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000364 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000365 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366}
367
368void SkPath::rewind() {
369 SkDEBUGCODE(this->validate();)
370
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000371 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000372 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373}
374
fsb1475b02016-01-20 09:51:07 -0800375bool SkPath::isLastContourClosed() const {
376 int verbCount = fPathRef->countVerbs();
377 if (0 == verbCount) {
378 return false;
379 }
380 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
381}
382
reed@google.com7e6c4d12012-05-10 14:05:43 +0000383bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000384 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000385
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000386 if (2 == verbCount) {
387 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
388 if (kLine_Verb == fPathRef->atVerb(1)) {
389 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000390 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000391 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000392 line[0] = pts[0];
393 line[1] = pts[1];
394 }
395 return true;
396 }
397 }
398 return false;
399}
400
caryclark@google.comf1316942011-07-26 19:54:45 +0000401/*
402 Determines if path is a rect by keeping track of changes in direction
403 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 The direction is computed such that:
406 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000407 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000409 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000410
caryclark@google.comf1316942011-07-26 19:54:45 +0000411A rectangle cycles up/right/down/left or up/left/down/right.
412
413The test fails if:
414 The path is closed, and followed by a line.
415 A second move creates a new endpoint.
416 A diagonal line is parsed.
417 There's more than four changes of direction.
418 There's a discontinuity on the line (e.g., a move in the middle)
419 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000420 The path contains a quadratic or cubic.
421 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000422 *The rectangle doesn't complete a cycle.
423 *The final point isn't equal to the first point.
424
425 *These last two conditions we relax if we have a 3-edge path that would
426 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428It's OK if the path has:
429 Several colinear line segments composing a rectangle side.
430 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000431
caryclark@google.comf1316942011-07-26 19:54:45 +0000432The direction takes advantage of the corners found since opposite sides
433must travel in opposite directions.
434
435FIXME: Allow colinear quads and cubics to be treated like lines.
436FIXME: If the API passes fill-only, return true if the filled stroke
437 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000438
Cary Clark48c464a2018-04-16 12:06:07 -0400439 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000440 0x1 is set if the segment is horizontal
441 0x2 is set if the segment is moving to the right or down
442 thus:
443 two directions are opposites iff (dirA ^ dirB) == 0x2
444 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000445
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000447static int rect_make_dir(SkScalar dx, SkScalar dy) {
448 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
449}
Cary Clark88ba9712018-04-12 14:00:24 -0400450
caryclark@google.comf68154a2012-11-21 15:18:06 +0000451bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400452 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400454 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400455 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400456 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
457 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400458 SkPoint firstCorner;
459 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000460 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400461 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400462 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400463 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000464 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000465 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700466 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000467 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000468 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700469 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
470 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000472 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700474 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400476 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400477 lastPt = pts;
478 }
Cary Clark88ba9712018-04-12 14:00:24 -0400479 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
480 SkVector lineDelta = lineEnd - lineStart;
481 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 return false; // diagonal
483 }
Cary Clark1eece782018-04-20 10:14:41 -0400484 if (!lineDelta.isFinite()) {
485 return false; // path contains infinity or NaN
486 }
Cary Clark88ba9712018-04-12 14:00:24 -0400487 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 break; // single point on side OK
489 }
Cary Clark48c464a2018-04-16 12:06:07 -0400490 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400492 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 corners = 1;
494 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400495 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 break;
497 }
498 if (closedOrMoved) {
499 return false; // closed followed by a line
500 }
Cary Clark48c464a2018-04-16 12:06:07 -0400501 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000502 break; // colinear with first
503 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400505 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400506 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400507 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400508 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400509 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 break; // colinear segment
511 }
Cary Clark48c464a2018-04-16 12:06:07 -0400512 directions[corners++] = nextDirection;
513 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400514 switch (corners) {
515 case 2:
516 firstCorner = lineStart;
517 break;
518 case 3:
519 if ((directions[0] ^ directions[2]) != 2) {
520 return false;
521 }
522 thirdCorner = lineEnd;
523 break;
524 case 4:
525 if ((directions[1] ^ directions[3]) != 2) {
526 return false;
527 }
528 break;
529 default:
530 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000531 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400532 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 break;
534 }
535 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000536 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 case kCubic_Verb:
538 return false; // quadratic, cubic not allowed
539 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400540 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700541 insertClose = true;
542 *currVerb -= 1; // try move again afterwards
543 goto addMissingClose;
544 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400545 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400546 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400547 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400548 closeXY = *firstPt - *lastPt;
549 if (closeXY.fX && closeXY.fY) {
550 return false; // we're diagonal, abort
551 }
Cary Clark8540e112018-04-11 14:30:27 -0400552 }
Cary Clark88ba9712018-04-12 14:00:24 -0400553 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000554 closedOrMoved = true;
555 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000556 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000557 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000558 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000559 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000560 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700561addMissingClose:
562 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000563 }
564 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400565 if (corners < 3 || corners > 4) {
566 return false;
567 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000568 if (savePts) {
569 *ptsPtr = savePts;
570 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400571 // check if close generates diagonal
572 closeXY = *firstPt - *lastPt;
573 if (closeXY.fX && closeXY.fY) {
574 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400575 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400576 if (rect) {
577 rect->set(firstCorner, thirdCorner);
578 }
579 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000580 *isClosed = autoClose;
581 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400582 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400583 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000584 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400585 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000586}
587
robertphillips4f662e62014-12-29 14:06:51 -0800588bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000589 SkDEBUGCODE(this->validate();)
590 int currVerb = 0;
591 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400592 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000593}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000594
caryclark95bc5f32015-04-08 08:34:15 -0700595bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000596 SkDEBUGCODE(this->validate();)
597 int currVerb = 0;
598 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000599 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400600 SkRect testRects[2];
601 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602 return false;
603 }
Cary Clark5c715182018-04-09 16:07:11 -0400604 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000605 if (testRects[0].contains(testRects[1])) {
606 if (rects) {
607 rects[0] = testRects[0];
608 rects[1] = testRects[1];
609 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000610 if (dirs) {
611 dirs[0] = testDirs[0];
612 dirs[1] = testDirs[1];
613 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000614 return true;
615 }
616 if (testRects[1].contains(testRects[0])) {
617 if (rects) {
618 rects[0] = testRects[1];
619 rects[1] = testRects[0];
620 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000621 if (dirs) {
622 dirs[0] = testDirs[1];
623 dirs[1] = testDirs[0];
624 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000625 return true;
626 }
627 }
628 return false;
629}
630
Mike Reed0c3137c2018-02-20 13:57:05 -0500631bool SkPath::isOval(SkRect* bounds) const {
632 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
633}
634
635bool SkPath::isRRect(SkRRect* rrect) const {
636 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
637}
638
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639int SkPath::countPoints() const {
640 return fPathRef->countPoints();
641}
642
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000643int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkDEBUGCODE(this->validate();)
645
646 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800649 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651}
652
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000653SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
655 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000656 }
657 return SkPoint::Make(0, 0);
658}
659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660int SkPath::countVerbs() const {
661 return fPathRef->countVerbs();
662}
663
664static inline void copy_verbs_reverse(uint8_t* inorderDst,
665 const uint8_t* reversedSrc,
666 int count) {
667 for (int i = 0; i < count; ++i) {
668 inorderDst[i] = reversedSrc[~i];
669 }
670}
671
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000672int SkPath::getVerbs(uint8_t dst[], int max) const {
673 SkDEBUGCODE(this->validate();)
674
675 SkASSERT(max >= 0);
676 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 int count = SkMin32(max, fPathRef->countVerbs());
678 copy_verbs_reverse(dst, fPathRef->verbs(), count);
679 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000680}
681
reed@google.com294dd7b2011-10-11 11:58:32 +0000682bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 SkDEBUGCODE(this->validate();)
684
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 if (count > 0) {
687 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000690 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000692 if (lastPt) {
693 lastPt->set(0, 0);
694 }
695 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696}
697
caryclarkaec25102015-04-29 08:28:30 -0700698void SkPath::setPt(int index, SkScalar x, SkScalar y) {
699 SkDEBUGCODE(this->validate();)
700
701 int count = fPathRef->countPoints();
702 if (count <= index) {
703 return;
704 } else {
705 SkPathRef::Editor ed(&fPathRef);
706 ed.atPoint(index)->set(x, y);
707 }
708}
709
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710void SkPath::setLastPt(SkScalar x, SkScalar y) {
711 SkDEBUGCODE(this->validate();)
712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000713 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 if (count == 0) {
715 this->moveTo(x, y);
716 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 SkPathRef::Editor ed(&fPathRef);
718 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 }
720}
721
reed@google.com04863fa2011-05-15 04:08:24 +0000722void SkPath::setConvexity(Convexity c) {
723 if (fConvexity != c) {
724 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000725 }
726}
727
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728//////////////////////////////////////////////////////////////////////////////
729// Construction methods
730
reed026beb52015-06-10 14:23:15 -0700731#define DIRTY_AFTER_EDIT \
732 do { \
733 fConvexity = kUnknown_Convexity; \
734 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000735 } while (0)
736
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737void SkPath::incReserve(U16CPU inc) {
738 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000739 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740 SkDEBUGCODE(this->validate();)
741}
742
743void SkPath::moveTo(SkScalar x, SkScalar y) {
744 SkDEBUGCODE(this->validate();)
745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000746 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000748 // remember our index
749 fLastMoveToIndex = fPathRef->countPoints();
750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700752
753 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754}
755
756void SkPath::rMoveTo(SkScalar x, SkScalar y) {
757 SkPoint pt;
758 this->getLastPt(&pt);
759 this->moveTo(pt.fX + x, pt.fY + y);
760}
761
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000762void SkPath::injectMoveToIfNeeded() {
763 if (fLastMoveToIndex < 0) {
764 SkScalar x, y;
765 if (fPathRef->countVerbs() == 0) {
766 x = y = 0;
767 } else {
768 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
769 x = pt.fX;
770 y = pt.fY;
771 }
772 this->moveTo(x, y);
773 }
774}
775
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776void SkPath::lineTo(SkScalar x, SkScalar y) {
777 SkDEBUGCODE(this->validate();)
778
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000779 this->injectMoveToIfNeeded();
780
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000781 SkPathRef::Editor ed(&fPathRef);
782 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783
reed@google.comb54455e2011-05-16 14:16:04 +0000784 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785}
786
787void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000788 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 SkPoint pt;
790 this->getLastPt(&pt);
791 this->lineTo(pt.fX + x, pt.fY + y);
792}
793
794void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
795 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000796
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000797 this->injectMoveToIfNeeded();
798
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000799 SkPathRef::Editor ed(&fPathRef);
800 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 pts[0].set(x1, y1);
802 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000803
reed@google.comb54455e2011-05-16 14:16:04 +0000804 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805}
806
807void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000808 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 SkPoint pt;
810 this->getLastPt(&pt);
811 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
812}
813
reed@google.com277c3f82013-05-31 15:17:50 +0000814void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
815 SkScalar w) {
816 // check for <= 0 or NaN with this test
817 if (!(w > 0)) {
818 this->lineTo(x2, y2);
819 } else if (!SkScalarIsFinite(w)) {
820 this->lineTo(x1, y1);
821 this->lineTo(x2, y2);
822 } else if (SK_Scalar1 == w) {
823 this->quadTo(x1, y1, x2, y2);
824 } else {
825 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000826
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000827 this->injectMoveToIfNeeded();
828
reed@google.com277c3f82013-05-31 15:17:50 +0000829 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000830 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000831 pts[0].set(x1, y1);
832 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000833
reed@google.com277c3f82013-05-31 15:17:50 +0000834 DIRTY_AFTER_EDIT;
835 }
836}
837
838void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
839 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000840 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000841 SkPoint pt;
842 this->getLastPt(&pt);
843 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
844}
845
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
847 SkScalar x3, SkScalar y3) {
848 SkDEBUGCODE(this->validate();)
849
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000850 this->injectMoveToIfNeeded();
851
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000852 SkPathRef::Editor ed(&fPathRef);
853 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 pts[0].set(x1, y1);
855 pts[1].set(x2, y2);
856 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857
reed@google.comb54455e2011-05-16 14:16:04 +0000858 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859}
860
861void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
862 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000863 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 SkPoint pt;
865 this->getLastPt(&pt);
866 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
867 pt.fX + x3, pt.fY + y3);
868}
869
870void SkPath::close() {
871 SkDEBUGCODE(this->validate();)
872
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000873 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000875 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 case kLine_Verb:
877 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000878 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000880 case kMove_Verb: {
881 SkPathRef::Editor ed(&fPathRef);
882 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 }
reed@google.com277c3f82013-05-31 15:17:50 +0000885 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000886 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000887 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000888 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000889 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000890 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891 }
892 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000893
894 // signal that we need a moveTo to follow us (unless we're done)
895#if 0
896 if (fLastMoveToIndex >= 0) {
897 fLastMoveToIndex = ~fLastMoveToIndex;
898 }
899#else
900 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
901#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902}
903
904///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000905
fmalitac08d53e2015-11-17 09:53:29 -0800906namespace {
907
908template <unsigned N>
909class PointIterator {
910public:
911 PointIterator(SkPath::Direction dir, unsigned startIndex)
912 : fCurrent(startIndex % N)
913 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
914
915 const SkPoint& current() const {
916 SkASSERT(fCurrent < N);
917 return fPts[fCurrent];
918 }
919
920 const SkPoint& next() {
921 fCurrent = (fCurrent + fAdvance) % N;
922 return this->current();
923 }
924
925protected:
926 SkPoint fPts[N];
927
928private:
929 unsigned fCurrent;
930 unsigned fAdvance;
931};
932
933class RectPointIterator : public PointIterator<4> {
934public:
935 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
936 : PointIterator(dir, startIndex) {
937
938 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
939 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
940 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
941 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
942 }
943};
944
945class OvalPointIterator : public PointIterator<4> {
946public:
947 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
948 : PointIterator(dir, startIndex) {
949
950 const SkScalar cx = oval.centerX();
951 const SkScalar cy = oval.centerY();
952
953 fPts[0] = SkPoint::Make(cx, oval.fTop);
954 fPts[1] = SkPoint::Make(oval.fRight, cy);
955 fPts[2] = SkPoint::Make(cx, oval.fBottom);
956 fPts[3] = SkPoint::Make(oval.fLeft, cy);
957 }
958};
959
960class RRectPointIterator : public PointIterator<8> {
961public:
962 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
963 : PointIterator(dir, startIndex) {
964
965 const SkRect& bounds = rrect.getBounds();
966 const SkScalar L = bounds.fLeft;
967 const SkScalar T = bounds.fTop;
968 const SkScalar R = bounds.fRight;
969 const SkScalar B = bounds.fBottom;
970
971 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
972 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
973 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
974 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
975 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
976 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
977 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
978 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
979 }
980};
981
982} // anonymous namespace
983
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000984static void assert_known_direction(int dir) {
985 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
986}
987
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800989 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990}
991
992void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
993 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800994 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
995}
996
997void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000998 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700999 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001000 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001001 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001002 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001003
fmalitac08d53e2015-11-17 09:53:29 -08001004 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005
fmalitac08d53e2015-11-17 09:53:29 -08001006 const int kVerbs = 5; // moveTo + 3x lineTo + close
1007 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008
fmalitac08d53e2015-11-17 09:53:29 -08001009 RectPointIterator iter(rect, dir, startIndex);
1010
1011 this->moveTo(iter.current());
1012 this->lineTo(iter.next());
1013 this->lineTo(iter.next());
1014 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001015 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001016
1017 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018}
1019
reed@google.com744faba2012-05-29 19:54:52 +00001020void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1021 SkDEBUGCODE(this->validate();)
1022 if (count <= 0) {
1023 return;
1024 }
1025
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001026 fLastMoveToIndex = fPathRef->countPoints();
1027
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001028 // +close makes room for the extra kClose_Verb
1029 SkPathRef::Editor ed(&fPathRef, count+close, count);
1030
1031 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001032 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001033 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1034 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001035 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001036
reed@google.com744faba2012-05-29 19:54:52 +00001037 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001038 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001039 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001040 }
1041
reed@google.com744faba2012-05-29 19:54:52 +00001042 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001043 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001044}
1045
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001046#include "SkGeometry.h"
1047
reedf90ea012015-01-29 12:03:58 -08001048static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1049 SkPoint* pt) {
1050 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001051 // Chrome uses this path to move into and out of ovals. If not
1052 // treated as a special case the moves can distort the oval's
1053 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001054 pt->set(oval.fRight, oval.centerY());
1055 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001056 } else if (0 == oval.width() && 0 == oval.height()) {
1057 // Chrome will sometimes create 0 radius round rects. Having degenerate
1058 // quad segments in the path prevents the path from being recognized as
1059 // a rect.
1060 // TODO: optimizing the case where only one of width or height is zero
1061 // should also be considered. This case, however, doesn't seem to be
1062 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001063 pt->set(oval.fRight, oval.fTop);
1064 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001065 }
reedf90ea012015-01-29 12:03:58 -08001066 return false;
1067}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001068
reedd5d27d92015-02-09 13:54:43 -08001069// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1070//
1071static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1072 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1073 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1074 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001075
1076 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001077 loss in radians-conversion and/or sin/cos, we may end up with coincident
1078 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1079 of drawing a nearly complete circle (good).
1080 e.g. canvas.drawArc(0, 359.99, ...)
1081 -vs- canvas.drawArc(0, 359.9, ...)
1082 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001083 */
reedd5d27d92015-02-09 13:54:43 -08001084 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001085 SkScalar sw = SkScalarAbs(sweepAngle);
1086 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1087 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1088 // make a guess at a tiny angle (in radians) to tweak by
1089 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1090 // not sure how much will be enough, so we use a loop
1091 do {
1092 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001093 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1094 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001095 }
1096 }
reedd5d27d92015-02-09 13:54:43 -08001097 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1098}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099
reed9e779d42015-02-17 11:43:14 -08001100/**
1101 * If this returns 0, then the caller should just line-to the singlePt, else it should
1102 * ignore singlePt and append the specified number of conics.
1103 */
reedd5d27d92015-02-09 13:54:43 -08001104static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001105 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1106 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001107 SkMatrix matrix;
1108
1109 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1110 matrix.postTranslate(oval.centerX(), oval.centerY());
1111
reed9e779d42015-02-17 11:43:14 -08001112 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1113 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001114 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001115 }
1116 return count;
reedd5d27d92015-02-09 13:54:43 -08001117}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001118
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001121 SkRRect rrect;
1122 rrect.setRectRadii(rect, (const SkVector*) radii);
1123 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124}
1125
reed@google.com4ed0fb72012-12-12 20:48:18 +00001126void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001127 // legacy start indices: 6 (CW) and 7(CCW)
1128 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1129}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001130
fmalitac08d53e2015-11-17 09:53:29 -08001131void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1132 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001133
caryclarkda707bf2015-11-19 14:47:43 -08001134 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001135 const SkRect& bounds = rrect.getBounds();
1136
Brian Salomon0a241ce2017-12-15 11:31:05 -05001137 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001138 // degenerate(rect) => radii points are collapsing
1139 this->addRect(bounds, dir, (startIndex + 1) / 2);
1140 } else if (rrect.isOval()) {
1141 // degenerate(oval) => line points are collapsing
1142 this->addOval(bounds, dir, startIndex / 2);
1143 } else {
1144 fFirstDirection = this->hasOnlyMoveTos() ?
1145 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1146
1147 SkAutoPathBoundsUpdate apbu(this, bounds);
1148 SkAutoDisableDirectionCheck addc(this);
1149
1150 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1151 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1152 const SkScalar weight = SK_ScalarRoot2Over2;
1153
1154 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1155 const int kVerbs = startsWithConic
1156 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1157 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1158 this->incReserve(kVerbs);
1159
1160 RRectPointIterator rrectIter(rrect, dir, startIndex);
1161 // Corner iterator indices follow the collapsed radii model,
1162 // adjusted such that the start pt is "behind" the radii start pt.
1163 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1164 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1165
1166 this->moveTo(rrectIter.current());
1167 if (startsWithConic) {
1168 for (unsigned i = 0; i < 3; ++i) {
1169 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1170 this->lineTo(rrectIter.next());
1171 }
1172 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1173 // final lineTo handled by close().
1174 } else {
1175 for (unsigned i = 0; i < 4; ++i) {
1176 this->lineTo(rrectIter.next());
1177 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1178 }
1179 }
1180 this->close();
1181
caryclarkda707bf2015-11-19 14:47:43 -08001182 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001183 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001184
fmalitac08d53e2015-11-17 09:53:29 -08001185 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1186 }
1187
1188 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001189}
1190
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001192 int count = fPathRef->countVerbs();
1193 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1194 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001195 if (*verbs == kLine_Verb ||
1196 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001197 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001198 *verbs == kCubic_Verb) {
1199 return false;
1200 }
1201 ++verbs;
1202 }
1203 return true;
1204}
1205
Brian Osmana2318572017-07-10 15:09:26 -04001206bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1207 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001208 if (count < 2) {
1209 return true;
1210 }
Brian Osmana2318572017-07-10 15:09:26 -04001211 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001212 const SkPoint& first = *pts;
1213 for (int index = 1; index < count; ++index) {
1214 if (first != pts[index]) {
1215 return false;
1216 }
1217 }
1218 return true;
1219}
1220
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001221void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1222 Direction dir) {
1223 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001224
humper@google.com75e3ca12013-04-08 21:44:11 +00001225 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001226 return;
1227 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001228
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001229 SkRRect rrect;
1230 rrect.setRectXY(rect, rx, ry);
1231 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001232}
1233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001235 // legacy start index: 1
1236 this->addOval(oval, dir, 1);
1237}
1238
1239void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001240 assert_known_direction(dir);
1241
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001242 /* If addOval() is called after previous moveTo(),
1243 this path is still marked as an oval. This is used to
1244 fit into WebKit's calling sequences.
1245 We can't simply check isEmpty() in this case, as additional
1246 moveTo() would mark the path non empty.
1247 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001248 bool isOval = hasOnlyMoveTos();
1249 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001250 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001251 } else {
reed026beb52015-06-10 14:23:15 -07001252 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001253 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001254
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001255 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001256 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257
fmalitac08d53e2015-11-17 09:53:29 -08001258 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1259 const int kVerbs = 6; // moveTo + 4x conicTo + close
1260 this->incReserve(kVerbs);
1261
1262 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1263 // The corner iterator pts are tracking "behind" the oval/radii pts.
1264 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001265 const SkScalar weight = SK_ScalarRoot2Over2;
1266
fmalitac08d53e2015-11-17 09:53:29 -08001267 this->moveTo(ovalIter.current());
1268 for (unsigned i = 0; i < 4; ++i) {
1269 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001270 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272
fmalitac08d53e2015-11-17 09:53:29 -08001273 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1274
robertphillips@google.com466310d2013-12-03 16:43:54 +00001275 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001276
bsalomon78d58d12016-05-27 09:17:04 -07001277 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001278}
1279
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1281 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001282 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 }
1284}
1285
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1287 bool forceMoveTo) {
1288 if (oval.width() < 0 || oval.height() < 0) {
1289 return;
1290 }
1291
reedf90ea012015-01-29 12:03:58 -08001292 if (fPathRef->countVerbs() == 0) {
1293 forceMoveTo = true;
1294 }
1295
1296 SkPoint lonePt;
1297 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1298 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1299 return;
1300 }
1301
reedd5d27d92015-02-09 13:54:43 -08001302 SkVector startV, stopV;
1303 SkRotationDirection dir;
1304 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1305
reed9e779d42015-02-17 11:43:14 -08001306 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001307
Brian Salomone4949402018-04-26 15:22:04 -04001308 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1309 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1310 // arcs from the same oval.
1311 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1312 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001313 if (forceMoveTo) {
1314 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001315 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001316 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1317 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1318 this->lineTo(pt);
1319 }
1320 };
1321
xidachen6069dda2016-10-06 05:42:23 -07001322 // At this point, we know that the arc is not a lone point, but startV == stopV
1323 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1324 // cannot handle it.
1325 if (startV == stopV) {
1326 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1327 SkScalar radiusX = oval.width() / 2;
1328 SkScalar radiusY = oval.height() / 2;
1329 // We cannot use SkScalarSinCos function in the next line because
1330 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1331 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001332 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001333 // make sin(endAngle) to be 0 which will then draw a dot.
1334 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1335 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001336 addPt(singlePt);
xidachen6069dda2016-10-06 05:42:23 -07001337 return;
1338 }
1339
reedd5d27d92015-02-09 13:54:43 -08001340 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001341 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001342 if (count) {
1343 this->incReserve(count * 2 + 1);
1344 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001345 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001346 for (int i = 0; i < count; ++i) {
1347 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1348 }
reed9e779d42015-02-17 11:43:14 -08001349 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001350 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001351 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352}
1353
caryclark55d49052016-01-23 05:07:04 -08001354// This converts the SVG arc to conics.
1355// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1356// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1357// See also SVG implementation notes:
1358// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1359// Note that arcSweep bool value is flipped from the original implementation.
1360void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1361 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001362 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001363 SkPoint srcPts[2];
1364 this->getLastPt(&srcPts[0]);
1365 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1366 // joining the endpoints.
1367 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1368 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001369 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001370 return;
1371 }
1372 // If the current point and target point for the arc are identical, it should be treated as a
1373 // zero length path. This ensures continuity in animations.
1374 srcPts[1].set(x, y);
1375 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001376 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001377 return;
1378 }
1379 rx = SkScalarAbs(rx);
1380 ry = SkScalarAbs(ry);
1381 SkVector midPointDistance = srcPts[0] - srcPts[1];
1382 midPointDistance *= 0.5f;
1383
1384 SkMatrix pointTransform;
1385 pointTransform.setRotate(-angle);
1386
1387 SkPoint transformedMidPoint;
1388 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1389 SkScalar squareRx = rx * rx;
1390 SkScalar squareRy = ry * ry;
1391 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1392 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1393
1394 // Check if the radii are big enough to draw the arc, scale radii if not.
1395 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1396 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1397 if (radiiScale > 1) {
1398 radiiScale = SkScalarSqrt(radiiScale);
1399 rx *= radiiScale;
1400 ry *= radiiScale;
1401 }
1402
1403 pointTransform.setScale(1 / rx, 1 / ry);
1404 pointTransform.preRotate(-angle);
1405
1406 SkPoint unitPts[2];
1407 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1408 SkVector delta = unitPts[1] - unitPts[0];
1409
1410 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1411 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1412
1413 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1414 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1415 scaleFactor = -scaleFactor;
1416 }
1417 delta.scale(scaleFactor);
1418 SkPoint centerPoint = unitPts[0] + unitPts[1];
1419 centerPoint *= 0.5f;
1420 centerPoint.offset(-delta.fY, delta.fX);
1421 unitPts[0] -= centerPoint;
1422 unitPts[1] -= centerPoint;
1423 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1424 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1425 SkScalar thetaArc = theta2 - theta1;
1426 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1427 thetaArc += SK_ScalarPI * 2;
1428 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1429 thetaArc -= SK_ScalarPI * 2;
1430 }
1431 pointTransform.setRotate(angle);
1432 pointTransform.preScale(rx, ry);
1433
Cary Clark1acd3c72017-12-08 11:37:01 -05001434#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001435 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001436#else
1437 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1438 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1439#endif
caryclark55d49052016-01-23 05:07:04 -08001440 SkScalar thetaWidth = thetaArc / segments;
1441 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1442 if (!SkScalarIsFinite(t)) {
1443 return;
1444 }
1445 SkScalar startTheta = theta1;
1446 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001447#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1448 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1449 return scalar == SkScalarFloorToScalar(scalar);
1450 };
1451 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1452 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1453 scalar_is_integer(x) && scalar_is_integer(y);
1454#endif
caryclark55d49052016-01-23 05:07:04 -08001455 for (int i = 0; i < segments; ++i) {
1456 SkScalar endTheta = startTheta + thetaWidth;
1457 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1458
1459 unitPts[1].set(cosEndTheta, sinEndTheta);
1460 unitPts[1] += centerPoint;
1461 unitPts[0] = unitPts[1];
1462 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1463 SkPoint mapped[2];
1464 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001465 /*
1466 Computing the arc width introduces rounding errors that cause arcs to start
1467 outside their marks. A round rect may lose convexity as a result. If the input
1468 values are on integers, place the conic on integers as well.
1469 */
1470#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1471 if (expectIntegers) {
1472 SkScalar* mappedScalars = &mapped[0].fX;
1473 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1474 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1475 }
1476 }
1477#endif
caryclark55d49052016-01-23 05:07:04 -08001478 this->conicTo(mapped[0], mapped[1], w);
1479 startTheta = endTheta;
1480 }
1481}
1482
1483void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1484 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1485 SkPoint currentPoint;
1486 this->getLastPt(&currentPoint);
1487 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1488}
1489
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001490void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 if (oval.isEmpty() || 0 == sweepAngle) {
1492 return;
1493 }
1494
1495 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1496
1497 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001498 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1499 // See SkPath::addOval() docs.
1500 SkScalar startOver90 = startAngle / 90.f;
1501 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1502 SkScalar error = startOver90 - startOver90I;
1503 if (SkScalarNearlyEqual(error, 0)) {
1504 // Index 1 is at startAngle == 0.
1505 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1506 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1507 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1508 (unsigned) startIndex);
1509 return;
1510 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 }
bsalomon1978ce22016-05-31 10:42:16 -07001512 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513}
1514
1515/*
1516 Need to handle the case when the angle is sharp, and our computed end-points
1517 for the arc go behind pt1 and/or p2...
1518*/
reedc7789042015-01-29 12:59:11 -08001519void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001520 if (radius == 0) {
1521 this->lineTo(x1, y1);
1522 return;
1523 }
1524
1525 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 // need to know our prev pt so we can construct tangent vectors
1528 {
1529 SkPoint start;
1530 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001531 // Handle degenerate cases by adding a line to the first point and
1532 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 before.setNormalize(x1 - start.fX, y1 - start.fY);
1534 after.setNormalize(x2 - x1, y2 - y1);
1535 }
reed@google.comabf15c12011-01-18 20:35:51 +00001536
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 SkScalar cosh = SkPoint::DotProduct(before, after);
1538 SkScalar sinh = SkPoint::CrossProduct(before, after);
1539
1540 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001541 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 return;
1543 }
reed@google.comabf15c12011-01-18 20:35:51 +00001544
Mike Reeda99b6ce2017-02-04 11:04:26 -05001545 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546
Mike Reeda99b6ce2017-02-04 11:04:26 -05001547 SkScalar xx = x1 - dist * before.fX;
1548 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001549 after.setLength(dist);
1550 this->lineTo(xx, yy);
1551 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1552 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553}
1554
1555///////////////////////////////////////////////////////////////////////////////
1556
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001557void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 SkMatrix matrix;
1559
1560 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001561 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562}
1563
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001564void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001567 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 SkPoint pts[4];
1569 Verb verb;
1570
Cary Clark9480d822017-10-19 18:01:13 -04001571 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001572 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 while ((verb = iter.next(pts)) != kDone_Verb) {
1574 switch (verb) {
1575 case kMove_Verb:
1576 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001577 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1578 injectMoveToIfNeeded(); // In case last contour is closed
1579 this->lineTo(pts[0]);
1580 } else {
1581 this->moveTo(pts[0]);
1582 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 break;
1584 case kLine_Verb:
1585 proc(matrix, &pts[1], &pts[1], 1);
1586 this->lineTo(pts[1]);
1587 break;
1588 case kQuad_Verb:
1589 proc(matrix, &pts[1], &pts[1], 2);
1590 this->quadTo(pts[1], pts[2]);
1591 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001592 case kConic_Verb:
1593 proc(matrix, &pts[1], &pts[1], 2);
1594 this->conicTo(pts[1], pts[2], iter.conicWeight());
1595 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 case kCubic_Verb:
1597 proc(matrix, &pts[1], &pts[1], 3);
1598 this->cubicTo(pts[1], pts[2], pts[3]);
1599 break;
1600 case kClose_Verb:
1601 this->close();
1602 break;
1603 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001604 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001606 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 }
1608}
1609
1610///////////////////////////////////////////////////////////////////////////////
1611
reed@google.com277c3f82013-05-31 15:17:50 +00001612static int pts_in_verb(unsigned verb) {
1613 static const uint8_t gPtsInVerb[] = {
1614 1, // kMove
1615 1, // kLine
1616 2, // kQuad
1617 2, // kConic
1618 3, // kCubic
1619 0, // kClose
1620 0 // kDone
1621 };
1622
1623 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1624 return gPtsInVerb[verb];
1625}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627// ignore the last point of the 1st contour
1628void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001629 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1630 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 return;
1632 }
caryclark51c56782016-11-07 05:09:28 -08001633 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1634 SkASSERT(verbsEnd[0] == kMove_Verb);
1635 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1636 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637
caryclark51c56782016-11-07 05:09:28 -08001638 while (verbs < verbsEnd) {
1639 uint8_t v = *verbs++;
1640 pts -= pts_in_verb(v);
1641 switch (v) {
1642 case kMove_Verb:
1643 // if the path has multiple contours, stop after reversing the last
1644 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001646 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 break;
1648 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001649 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001651 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001652 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001653 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001655 this->cubicTo(pts[2], pts[1], pts[0]);
1656 break;
1657 case kClose_Verb:
1658 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 break;
1660 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001661 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 break;
1663 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 }
1665}
1666
reed@google.com63d73742012-01-10 15:33:12 +00001667void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001670 const SkPoint* pts = src.fPathRef->pointsEnd();
1671 // we will iterator through src's verbs backwards
1672 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1673 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001674 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001675
1676 bool needMove = true;
1677 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001678 while (verbs < verbsEnd) {
1679 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001680 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001681
1682 if (needMove) {
1683 --pts;
1684 this->moveTo(pts->fX, pts->fY);
1685 needMove = false;
1686 }
1687 pts -= n;
1688 switch (v) {
1689 case kMove_Verb:
1690 if (needClose) {
1691 this->close();
1692 needClose = false;
1693 }
1694 needMove = true;
1695 pts += 1; // so we see the point in "if (needMove)" above
1696 break;
1697 case kLine_Verb:
1698 this->lineTo(pts[0]);
1699 break;
1700 case kQuad_Verb:
1701 this->quadTo(pts[1], pts[0]);
1702 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001703 case kConic_Verb:
1704 this->conicTo(pts[1], pts[0], *--conicWeights);
1705 break;
reed@google.com63d73742012-01-10 15:33:12 +00001706 case kCubic_Verb:
1707 this->cubicTo(pts[2], pts[1], pts[0]);
1708 break;
1709 case kClose_Verb:
1710 needClose = true;
1711 break;
1712 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001713 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001714 }
1715 }
1716}
1717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718///////////////////////////////////////////////////////////////////////////////
1719
1720void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1721 SkMatrix matrix;
1722
1723 matrix.setTranslate(dx, dy);
1724 this->transform(matrix, dst);
1725}
1726
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1728 int level = 2) {
1729 if (--level >= 0) {
1730 SkPoint tmp[7];
1731
1732 SkChopCubicAtHalf(pts, tmp);
1733 subdivide_cubic_to(path, &tmp[0], level);
1734 subdivide_cubic_to(path, &tmp[3], level);
1735 } else {
1736 path->cubicTo(pts[1], pts[2], pts[3]);
1737 }
1738}
1739
1740void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1741 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001742 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 dst = (SkPath*)this;
1744 }
1745
tomhudson@google.com8d430182011-06-06 19:11:19 +00001746 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 SkPath tmp;
1748 tmp.fFillType = fFillType;
1749
1750 SkPath::Iter iter(*this, false);
1751 SkPoint pts[4];
1752 SkPath::Verb verb;
1753
reed@google.com4a3b7142012-05-16 17:16:46 +00001754 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 switch (verb) {
1756 case kMove_Verb:
1757 tmp.moveTo(pts[0]);
1758 break;
1759 case kLine_Verb:
1760 tmp.lineTo(pts[1]);
1761 break;
1762 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001763 // promote the quad to a conic
1764 tmp.conicTo(pts[1], pts[2],
1765 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001767 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001768 tmp.conicTo(pts[1], pts[2],
1769 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001770 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 case kCubic_Verb:
1772 subdivide_cubic_to(&tmp, pts);
1773 break;
1774 case kClose_Verb:
1775 tmp.close();
1776 break;
1777 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001778 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 break;
1780 }
1781 }
1782
1783 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001784 SkPathRef::Editor ed(&dst->fPathRef);
1785 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001786 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1789
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001792 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001793 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001795
reed026beb52015-06-10 14:23:15 -07001796 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1797 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001798 } else {
1799 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001800 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1801 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001802 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001803 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1804 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001805 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001806 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001807 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001808 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001809 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001810 }
1811 }
1812
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 SkDEBUGCODE(dst->validate();)
1814 }
1815}
1816
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817///////////////////////////////////////////////////////////////////////////////
1818///////////////////////////////////////////////////////////////////////////////
1819
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820SkPath::Iter::Iter() {
1821#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001822 fPts = nullptr;
1823 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001826 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827#endif
1828 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001829 fVerbs = nullptr;
1830 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831 fNeedClose = false;
1832}
1833
1834SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1835 this->setPath(path, forceClose);
1836}
1837
1838void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001839 fPts = path.fPathRef->points();
1840 fVerbs = path.fPathRef->verbs();
1841 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001842 fConicWeights = path.fPathRef->conicWeights();
1843 if (fConicWeights) {
1844 fConicWeights -= 1; // begin one behind
1845 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001847 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 fForceClose = SkToU8(forceClose);
1849 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001850 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851}
1852
1853bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001854 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 return false;
1856 }
1857 if (fForceClose) {
1858 return true;
1859 }
1860
1861 const uint8_t* verbs = fVerbs;
1862 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001863
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864 if (kMove_Verb == *(verbs - 1)) {
1865 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 }
1867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 while (verbs > stop) {
1869 // verbs points one beyond the current verb, decrement first.
1870 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871 if (kMove_Verb == v) {
1872 break;
1873 }
1874 if (kClose_Verb == v) {
1875 return true;
1876 }
1877 }
1878 return false;
1879}
1880
1881SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001882 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001884 // A special case: if both points are NaN, SkPoint::operation== returns
1885 // false, but the iterator expects that they are treated as the same.
1886 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001887 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1888 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001889 return kClose_Verb;
1890 }
1891
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 pts[0] = fLastPt;
1893 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 fLastPt = fMoveTo;
1895 fCloseLine = true;
1896 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001897 } else {
1898 pts[0] = fMoveTo;
1899 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901}
1902
reed@google.com9e25dbf2012-05-15 17:05:38 +00001903const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 if (fSegmentState == kAfterMove_SegmentState) {
1905 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001909
1910 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1911 // Set the first return pt to the last pt of the previous primitive.
1912 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913}
1914
caryclarke8c56662015-07-14 11:19:26 -07001915void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 // We need to step over anything that will not move the current draw point
1917 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001918 const uint8_t* lastMoveVerb = nullptr;
1919 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001920 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 SkPoint lastPt = fLastPt;
1922 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001923 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 switch (verb) {
1925 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 // Keep a record of this most recent move
1927 lastMoveVerb = fVerbs;
1928 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001929 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001930 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 fPts++;
1933 break;
1934
1935 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001936 // A close when we are in a segment is always valid except when it
1937 // follows a move which follows a segment.
1938 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 return;
1940 }
1941 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 break;
1944
1945 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001946 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 if (lastMoveVerb) {
1948 fVerbs = lastMoveVerb;
1949 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001950 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 return;
1952 }
1953 return;
1954 }
1955 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 fPts++;
1958 break;
1959
reed@google.com277c3f82013-05-31 15:17:50 +00001960 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001962 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 if (lastMoveVerb) {
1964 fVerbs = lastMoveVerb;
1965 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001966 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 return;
1968 }
1969 return;
1970 }
1971 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001974 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001975 break;
1976
1977 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001978 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 if (lastMoveVerb) {
1980 fVerbs = lastMoveVerb;
1981 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001982 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 return;
1984 }
1985 return;
1986 }
1987 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001988 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001989 fPts += 3;
1990 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001991
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001993 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994 }
1995 }
1996}
1997
reed@google.com4a3b7142012-05-16 17:16:46 +00001998SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002000
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 // Close the curve if requested and if there is some curve to close
2003 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002004 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 return kLine_Verb;
2006 }
2007 fNeedClose = false;
2008 return kClose_Verb;
2009 }
2010 return kDone_Verb;
2011 }
2012
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002013 // fVerbs is one beyond the current verb, decrement first
2014 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002015 const SkPoint* SK_RESTRICT srcPts = fPts;
2016 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017
2018 switch (verb) {
2019 case kMove_Verb:
2020 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002021 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002022 verb = this->autoClose(pts);
2023 if (verb == kClose_Verb) {
2024 fNeedClose = false;
2025 }
2026 return (Verb)verb;
2027 }
2028 if (fVerbs == fVerbStop) { // might be a trailing moveto
2029 return kDone_Verb;
2030 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002031 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002032 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002034 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002035 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 fNeedClose = fForceClose;
2037 break;
2038 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002039 pts[0] = this->cons_moveTo();
2040 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 fLastPt = srcPts[0];
2042 fCloseLine = false;
2043 srcPts += 1;
2044 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002045 case kConic_Verb:
2046 fConicWeights += 1;
2047 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002049 pts[0] = this->cons_moveTo();
2050 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 fLastPt = srcPts[1];
2052 srcPts += 2;
2053 break;
2054 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002055 pts[0] = this->cons_moveTo();
2056 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 fLastPt = srcPts[2];
2058 srcPts += 3;
2059 break;
2060 case kClose_Verb:
2061 verb = this->autoClose(pts);
2062 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002063 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 } else {
2065 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002066 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002068 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069 break;
2070 }
2071 fPts = srcPts;
2072 return (Verb)verb;
2073}
2074
2075///////////////////////////////////////////////////////////////////////////////
2076
Ben Wagner4d1955c2017-03-10 13:08:15 -05002077#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002078#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002079#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002080
reed@google.com51bbe752013-01-17 22:07:50 +00002081static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002082 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002083 str->append(label);
2084 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002085
reed@google.com51bbe752013-01-17 22:07:50 +00002086 const SkScalar* values = &pts[0].fX;
2087 count *= 2;
2088
2089 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002090 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002091 if (i < count - 1) {
2092 str->append(", ");
2093 }
2094 }
Mike Reed405b9d22018-01-09 15:11:08 -05002095 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002096 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002097 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002098 }
caryclark08fa28c2014-10-23 13:08:56 -07002099 str->append(");");
reede05fed02014-12-15 07:59:53 -08002100 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002101 str->append(" // ");
2102 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002103 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002104 if (i < count - 1) {
2105 str->append(", ");
2106 }
2107 }
2108 if (conicWeight >= 0) {
2109 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002111 }
2112 }
2113 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002114}
2115
caryclarke9562592014-09-15 09:26:09 -07002116void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002117 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 Iter iter(*this, forceClose);
2119 SkPoint pts[4];
2120 Verb verb;
2121
reed@google.com51bbe752013-01-17 22:07:50 +00002122 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002123 char const * const gFillTypeStrs[] = {
2124 "Winding",
2125 "EvenOdd",
2126 "InverseWinding",
2127 "InverseEvenOdd",
2128 };
2129 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2130 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002131 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 switch (verb) {
2133 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002134 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002142 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002144 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 break;
2148 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002149 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 default:
2152 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2153 verb = kDone_Verb; // stop the loop
2154 break;
2155 }
caryclark1049f122015-04-20 08:31:59 -07002156 if (!wStream && builder.size()) {
2157 SkDebugf("%s", builder.c_str());
2158 builder.reset();
2159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 }
caryclark66a5d8b2014-06-24 08:30:15 -07002161 if (wStream) {
2162 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002163 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164}
2165
reed@android.come522ca52009-11-23 20:10:41 +00002166void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002167 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002168}
2169
2170void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002171 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002172}
2173
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002174
Cary Clark0461e9f2017-08-25 15:13:38 -04002175bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002176 if ((fFillType & ~3) != 0) {
2177 return false;
2178 }
reed@google.comabf15c12011-01-18 20:35:51 +00002179
djsollen@google.com077348c2012-10-22 20:23:32 +00002180#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002181 if (!fBoundsIsDirty) {
2182 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002183
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002184 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002185 if (SkToBool(fIsFinite) != isFinite) {
2186 return false;
2187 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002188
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002189 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002190 // if we're empty, fBounds may be empty but translated, so we can't
2191 // necessarily compare to bounds directly
2192 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2193 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002194 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2195 return false;
2196 }
reed@android.come522ca52009-11-23 20:10:41 +00002197 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002198 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002199 if (!fBounds.isEmpty()) {
2200 return false;
2201 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002203 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002204 if (!fBounds.contains(bounds)) {
2205 return false;
2206 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002207 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 }
reed@android.come522ca52009-11-23 20:10:41 +00002209 }
2210 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002211#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002212 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002213}
reed@android.come522ca52009-11-23 20:10:41 +00002214
reed@google.com04863fa2011-05-15 04:08:24 +00002215///////////////////////////////////////////////////////////////////////////////
2216
reed@google.com0b7b9822011-05-16 12:29:27 +00002217static int sign(SkScalar x) { return x < 0; }
2218#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002219
robertphillipsc506e302014-09-16 09:43:31 -07002220enum DirChange {
2221 kLeft_DirChange,
2222 kRight_DirChange,
2223 kStraight_DirChange,
2224 kBackwards_DirChange,
2225
2226 kInvalid_DirChange
2227};
2228
2229
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002230static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002231 // The error epsilon was empirically derived; worse case round rects
2232 // with a mid point outset by 2x float epsilon in tests had an error
2233 // of 12.
2234 const int epsilon = 16;
2235 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2236 return false;
2237 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002238 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002239 int aBits = SkFloatAs2sCompliment(compA);
2240 int bBits = SkFloatAs2sCompliment(compB);
2241 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002242}
2243
caryclarkb4216502015-03-02 13:02:34 -08002244static bool approximately_zero_when_compared_to(double x, double y) {
2245 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002246}
2247
caryclarkb4216502015-03-02 13:02:34 -08002248
reed@google.com04863fa2011-05-15 04:08:24 +00002249// only valid for a single contour
2250struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002251 Convexicator()
2252 : fPtCount(0)
2253 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002254 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002255 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002256 , fIsCurve(false)
2257 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002258 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 // warnings
djsollen2f124632016-04-29 13:53:05 -07002260 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002261 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002262 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002263 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002264 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002265
2266 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002267 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 }
2269
2270 SkPath::Convexity getConvexity() const { return fConvexity; }
2271
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002273 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002274
reed@google.com04863fa2011-05-15 04:08:24 +00002275 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002276 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002277 return;
2278 }
2279
2280 if (0 == fPtCount) {
2281 fCurrPt = pt;
2282 ++fPtCount;
2283 } else {
2284 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002285 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002286 if (!SkScalarIsFinite(lengthSqd)) {
2287 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002288 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002289 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002290 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002291 fCurrPt = pt;
2292 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002293 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 } else {
2295 SkASSERT(fPtCount > 2);
2296 this->addVec(vec);
2297 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002298
reed@google.com85b6e392011-05-15 20:25:17 +00002299 int sx = sign(vec.fX);
2300 int sy = sign(vec.fY);
2301 fDx += (sx != fSx);
2302 fDy += (sy != fSy);
2303 fSx = sx;
2304 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002305
reed@google.com85b6e392011-05-15 20:25:17 +00002306 if (fDx > 3 || fDy > 3) {
2307 fConvexity = SkPath::kConcave_Convexity;
2308 }
reed@google.com04863fa2011-05-15 04:08:24 +00002309 }
2310 }
2311 }
2312
2313 void close() {
2314 if (fPtCount > 2) {
2315 this->addVec(fFirstVec);
2316 }
2317 }
2318
caryclarkb4216502015-03-02 13:02:34 -08002319 DirChange directionChange(const SkVector& curVec) {
2320 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2321
2322 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2323 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2324 largest = SkTMax(largest, -smallest);
2325
2326 if (!almost_equal(largest, largest + cross)) {
2327 int sign = SkScalarSignAsInt(cross);
2328 if (sign) {
2329 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2330 }
2331 }
2332
2333 if (cross) {
2334 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2335 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2336 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2337 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2338 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2339 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2340 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2341 if (sign) {
2342 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2343 }
2344 }
2345 }
2346
Cary Clarkdf429f32017-11-08 11:44:31 -05002347 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2348 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2349 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2350 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002351 fLastVec.dot(curVec) < 0.0f) {
2352 return kBackwards_DirChange;
2353 }
2354
2355 return kStraight_DirChange;
2356 }
2357
Cary Clarkc8323aa2017-08-25 08:04:43 -04002358 bool hasBackwards() const {
2359 return fBackwards;
2360 }
caryclarkb4216502015-03-02 13:02:34 -08002361
caryclarkd3d1a982014-12-08 04:57:38 -08002362 bool isFinite() const {
2363 return fIsFinite;
2364 }
2365
caryclark5ccef572015-03-02 10:07:56 -08002366 void setCurve(bool isCurve) {
2367 fIsCurve = isCurve;
2368 }
2369
reed@google.com04863fa2011-05-15 04:08:24 +00002370private:
2371 void addVec(const SkVector& vec) {
2372 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002373 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002374 switch (dir) {
2375 case kLeft_DirChange: // fall through
2376 case kRight_DirChange:
2377 if (kInvalid_DirChange == fExpectedDir) {
2378 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002379 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2380 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002381 } else if (dir != fExpectedDir) {
2382 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002383 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002384 }
2385 fLastVec = vec;
2386 break;
2387 case kStraight_DirChange:
2388 break;
2389 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002390 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002391 // If any of the subsequent dir is non-backward, it'll be concave.
2392 // Otherwise, it's still convex.
2393 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002394 }
robertphillipsc506e302014-09-16 09:43:31 -07002395 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002396 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002397 break;
2398 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002399 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002400 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 }
2402 }
2403
caryclarkb4216502015-03-02 13:02:34 -08002404 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002405 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002407 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2408 // value with the current vec is deemed to be of a significant value.
2409 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002411 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002413 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002414 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002415 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002416 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002417 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002418};
2419
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002421 // Sometimes we think we need to calculate convexity but another thread already did.
2422 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2423 return c;
2424 }
2425
reed@google.com04863fa2011-05-15 04:08:24 +00002426 SkPoint pts[4];
2427 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002429
2430 int contourCount = 0;
2431 int count;
2432 Convexicator state;
2433
caryclarkd3d1a982014-12-08 04:57:38 -08002434 if (!isFinite()) {
2435 return kUnknown_Convexity;
2436 }
Brian Osman205a1262017-09-18 13:13:48 +00002437 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002438 switch (verb) {
2439 case kMove_Verb:
2440 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002441 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002442 return kConcave_Convexity;
2443 }
2444 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002445 // fall through
2446 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002447 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002448 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002449 break;
caryclark5ccef572015-03-02 10:07:56 -08002450 case kQuad_Verb:
2451 // fall through
2452 case kConic_Verb:
2453 // fall through
2454 case kCubic_Verb:
2455 count = 2 + (kCubic_Verb == verb);
2456 // As an additional enhancement, this could set curve true only
2457 // if the curve is nonlinear
2458 state.setCurve(true);
2459 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002460 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002461 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002462 state.close();
2463 count = 0;
2464 break;
2465 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002466 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002467 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002468 return kConcave_Convexity;
2469 }
2470
2471 for (int i = 1; i <= count; i++) {
2472 state.addPt(pts[i]);
2473 }
2474 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002475 if (!state.isFinite()) {
2476 return kUnknown_Convexity;
2477 }
reed@google.com04863fa2011-05-15 04:08:24 +00002478 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002479 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002480 return kConcave_Convexity;
2481 }
2482 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002483 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002484 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002485 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2486 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2487 fConvexity = Convexity::kConcave_Convexity;
2488 } else {
2489 fFirstDirection = state.getFirstDirection();
2490 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002491 }
2492 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002493}
reed@google.com69a99432012-01-10 18:00:10 +00002494
2495///////////////////////////////////////////////////////////////////////////////
2496
2497class ContourIter {
2498public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002499 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002500
2501 bool done() const { return fDone; }
2502 // if !done() then these may be called
2503 int count() const { return fCurrPtCount; }
2504 const SkPoint* pts() const { return fCurrPt; }
2505 void next();
2506
2507private:
2508 int fCurrPtCount;
2509 const SkPoint* fCurrPt;
2510 const uint8_t* fCurrVerb;
2511 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002512 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002513 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002514 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002515};
2516
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002517ContourIter::ContourIter(const SkPathRef& pathRef) {
2518 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002519 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002520 fCurrPt = pathRef.points();
2521 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002522 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002523 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002524 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002525 this->next();
2526}
2527
2528void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002529 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002530 fDone = true;
2531 }
2532 if (fDone) {
2533 return;
2534 }
2535
2536 // skip pts of prev contour
2537 fCurrPt += fCurrPtCount;
2538
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002539 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002540 int ptCount = 1; // moveTo
2541 const uint8_t* verbs = fCurrVerb;
2542
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002543 for (--verbs; verbs > fStopVerbs; --verbs) {
2544 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002545 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002546 goto CONTOUR_END;
2547 case SkPath::kLine_Verb:
2548 ptCount += 1;
2549 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002550 case SkPath::kConic_Verb:
2551 fCurrConicWeight += 1;
2552 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002553 case SkPath::kQuad_Verb:
2554 ptCount += 2;
2555 break;
2556 case SkPath::kCubic_Verb:
2557 ptCount += 3;
2558 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002559 case SkPath::kClose_Verb:
2560 break;
2561 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002562 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002563 break;
2564 }
2565 }
2566CONTOUR_END:
2567 fCurrPtCount = ptCount;
2568 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002569 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002570}
2571
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002572// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002573static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002574 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2575 // We may get 0 when the above subtracts underflow. We expect this to be
2576 // very rare and lazily promote to double.
2577 if (0 == cross) {
2578 double p0x = SkScalarToDouble(p0.fX);
2579 double p0y = SkScalarToDouble(p0.fY);
2580
2581 double p1x = SkScalarToDouble(p1.fX);
2582 double p1y = SkScalarToDouble(p1.fY);
2583
2584 double p2x = SkScalarToDouble(p2.fX);
2585 double p2y = SkScalarToDouble(p2.fY);
2586
2587 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2588 (p1y - p0y) * (p2x - p0x));
2589
2590 }
2591 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002592}
2593
reed@google.comc1ea60a2012-01-31 15:15:36 +00002594// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002595static int find_max_y(const SkPoint pts[], int count) {
2596 SkASSERT(count > 0);
2597 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002598 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002599 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002600 SkScalar y = pts[i].fY;
2601 if (y > max) {
2602 max = y;
2603 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002604 }
2605 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002606 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002607}
2608
reed@google.comcabaf1d2012-01-11 21:03:05 +00002609static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2610 int i = index;
2611 for (;;) {
2612 i = (i + inc) % n;
2613 if (i == index) { // we wrapped around, so abort
2614 break;
2615 }
2616 if (pts[index] != pts[i]) { // found a different point, success!
2617 break;
2618 }
2619 }
2620 return i;
2621}
2622
reed@google.comc1ea60a2012-01-31 15:15:36 +00002623/**
2624 * Starting at index, and moving forward (incrementing), find the xmin and
2625 * xmax of the contiguous points that have the same Y.
2626 */
2627static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2628 int* maxIndexPtr) {
2629 const SkScalar y = pts[index].fY;
2630 SkScalar min = pts[index].fX;
2631 SkScalar max = min;
2632 int minIndex = index;
2633 int maxIndex = index;
2634 for (int i = index + 1; i < n; ++i) {
2635 if (pts[i].fY != y) {
2636 break;
2637 }
2638 SkScalar x = pts[i].fX;
2639 if (x < min) {
2640 min = x;
2641 minIndex = i;
2642 } else if (x > max) {
2643 max = x;
2644 maxIndex = i;
2645 }
2646 }
2647 *maxIndexPtr = maxIndex;
2648 return minIndex;
2649}
2650
reed026beb52015-06-10 14:23:15 -07002651static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2652 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002653}
2654
reed@google.comac8543f2012-01-30 20:51:25 +00002655/*
2656 * We loop through all contours, and keep the computed cross-product of the
2657 * contour that contained the global y-max. If we just look at the first
2658 * contour, we may find one that is wound the opposite way (correctly) since
2659 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2660 * that is outer most (or at least has the global y-max) before we can consider
2661 * its cross product.
2662 */
reed026beb52015-06-10 14:23:15 -07002663bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002664 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2665 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002666 return true;
2667 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002668
2669 // don't want to pay the cost for computing this if it
2670 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002671 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2672 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002673 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002674 return false;
2675 }
reed@google.com69a99432012-01-10 18:00:10 +00002676
reed026beb52015-06-10 14:23:15 -07002677 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002678
reed@google.comac8543f2012-01-30 20:51:25 +00002679 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002680 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002681 SkScalar ymaxCross = 0;
2682
reed@google.com69a99432012-01-10 18:00:10 +00002683 for (; !iter.done(); iter.next()) {
2684 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002685 if (n < 3) {
2686 continue;
2687 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002688
reed@google.comcabaf1d2012-01-11 21:03:05 +00002689 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002690 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002691 int index = find_max_y(pts, n);
2692 if (pts[index].fY < ymax) {
2693 continue;
2694 }
2695
2696 // If there is more than 1 distinct point at the y-max, we take the
2697 // x-min and x-max of them and just subtract to compute the dir.
2698 if (pts[(index + 1) % n].fY == pts[index].fY) {
2699 int maxIndex;
2700 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2701 if (minIndex == maxIndex) {
2702 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002703 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002704 SkASSERT(pts[minIndex].fY == pts[index].fY);
2705 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2706 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2707 // we just subtract the indices, and let that auto-convert to
2708 // SkScalar, since we just want - or + to signal the direction.
2709 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002710 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002711 TRY_CROSSPROD:
2712 // Find a next and prev index to use for the cross-product test,
2713 // but we try to find pts that form non-zero vectors from pts[index]
2714 //
2715 // Its possible that we can't find two non-degenerate vectors, so
2716 // we have to guard our search (e.g. all the pts could be in the
2717 // same place).
2718
2719 // we pass n - 1 instead of -1 so we don't foul up % operator by
2720 // passing it a negative LH argument.
2721 int prev = find_diff_pt(pts, index, n, n - 1);
2722 if (prev == index) {
2723 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002724 continue;
2725 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002726 int next = find_diff_pt(pts, index, n, 1);
2727 SkASSERT(next != index);
2728 cross = cross_prod(pts[prev], pts[index], pts[next]);
2729 // if we get a zero and the points are horizontal, then we look at the spread in
2730 // x-direction. We really should continue to walk away from the degeneracy until
2731 // there is a divergence.
2732 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2733 // construct the subtract so we get the correct Direction below
2734 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002735 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002736 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002737
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002738 if (cross) {
2739 // record our best guess so far
2740 ymax = pts[index].fY;
2741 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002742 }
2743 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002744 if (ymaxCross) {
2745 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002746 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002747 return true;
2748 } else {
2749 return false;
2750 }
reed@google.comac8543f2012-01-30 20:51:25 +00002751}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752
2753///////////////////////////////////////////////////////////////////////////////
2754
caryclark9aacd902015-12-14 08:38:09 -08002755static bool between(SkScalar a, SkScalar b, SkScalar c) {
2756 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2757 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2758 return (a - b) * (c - b) <= 0;
2759}
2760
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002761static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2762 SkScalar t) {
2763 SkScalar A = c3 + 3*(c1 - c2) - c0;
2764 SkScalar B = 3*(c2 - c1 - c1 + c0);
2765 SkScalar C = 3*(c1 - c0);
2766 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002767 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768}
2769
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002770template <size_t N> static void find_minmax(const SkPoint pts[],
2771 SkScalar* minPtr, SkScalar* maxPtr) {
2772 SkScalar min, max;
2773 min = max = pts[0].fX;
2774 for (size_t i = 1; i < N; ++i) {
2775 min = SkMinScalar(min, pts[i].fX);
2776 max = SkMaxScalar(max, pts[i].fX);
2777 }
2778 *minPtr = min;
2779 *maxPtr = max;
2780}
2781
caryclark9cb5d752015-12-18 04:35:24 -08002782static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2783 if (start.fY == end.fY) {
2784 return between(start.fX, x, end.fX) && x != end.fX;
2785 } else {
2786 return x == start.fX && y == start.fY;
2787 }
2788}
2789
caryclark9aacd902015-12-14 08:38:09 -08002790static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002791 SkScalar y0 = pts[0].fY;
2792 SkScalar y3 = pts[3].fY;
2793
2794 int dir = 1;
2795 if (y0 > y3) {
2796 SkTSwap(y0, y3);
2797 dir = -1;
2798 }
2799 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 return 0;
2801 }
caryclark9cb5d752015-12-18 04:35:24 -08002802 if (checkOnCurve(x, y, pts[0], pts[3])) {
2803 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002804 return 0;
2805 }
caryclark9cb5d752015-12-18 04:35:24 -08002806 if (y == y3) {
2807 return 0;
2808 }
2809
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810 // quickreject or quickaccept
2811 SkScalar min, max;
2812 find_minmax<4>(pts, &min, &max);
2813 if (x < min) {
2814 return 0;
2815 }
2816 if (x > max) {
2817 return dir;
2818 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002821 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002822 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002823 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002824 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002825 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002826 if (SkScalarNearlyEqual(xt, x)) {
2827 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2828 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002829 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002830 }
2831 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002832 return xt < x ? dir : 0;
2833}
2834
caryclark9aacd902015-12-14 08:38:09 -08002835static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 SkPoint dst[10];
2837 int n = SkChopCubicAtYExtrema(pts, dst);
2838 int w = 0;
2839 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002840 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 }
2842 return w;
2843}
2844
caryclark9aacd902015-12-14 08:38:09 -08002845static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2846 SkASSERT(src);
2847 SkASSERT(t >= 0 && t <= 1);
2848 SkScalar src2w = src[2] * w;
2849 SkScalar C = src[0];
2850 SkScalar A = src[4] - 2 * src2w + C;
2851 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002852 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002853}
2854
2855
2856static double conic_eval_denominator(SkScalar w, SkScalar t) {
2857 SkScalar B = 2 * (w - 1);
2858 SkScalar C = 1;
2859 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002860 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002861}
2862
2863static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2864 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865 SkScalar y0 = pts[0].fY;
2866 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002867
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 int dir = 1;
2869 if (y0 > y2) {
2870 SkTSwap(y0, y2);
2871 dir = -1;
2872 }
caryclark9aacd902015-12-14 08:38:09 -08002873 if (y < y0 || y > y2) {
2874 return 0;
2875 }
caryclark9cb5d752015-12-18 04:35:24 -08002876 if (checkOnCurve(x, y, pts[0], pts[2])) {
2877 *onCurveCount += 1;
2878 return 0;
2879 }
caryclark9aacd902015-12-14 08:38:09 -08002880 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 return 0;
2882 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002883
caryclark9aacd902015-12-14 08:38:09 -08002884 SkScalar roots[2];
2885 SkScalar A = pts[2].fY;
2886 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2887 SkScalar C = pts[0].fY;
2888 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2889 B -= C; // B = b*w - w * yCept + yCept - a
2890 C -= y;
2891 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2892 SkASSERT(n <= 1);
2893 SkScalar xt;
2894 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002895 // zero roots are returned only when y0 == y
2896 // Need [0] if dir == 1
2897 // and [2] if dir == -1
2898 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002899 } else {
2900 SkScalar t = roots[0];
2901 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2902 }
2903 if (SkScalarNearlyEqual(xt, x)) {
2904 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2905 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002906 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002907 }
2908 }
2909 return xt < x ? dir : 0;
2910}
2911
2912static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2913 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2914 if (y0 == y1) {
2915 return true;
2916 }
2917 if (y0 < y1) {
2918 return y1 <= y2;
2919 } else {
2920 return y1 >= y2;
2921 }
2922}
2923
2924static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2925 int* onCurveCount) {
2926 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002927 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002928 // If the data points are very large, the conic may not be monotonic but may also
2929 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002930 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2931 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2932 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002933 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2934 }
2935 return w;
2936}
2937
2938static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2939 SkScalar y0 = pts[0].fY;
2940 SkScalar y2 = pts[2].fY;
2941
2942 int dir = 1;
2943 if (y0 > y2) {
2944 SkTSwap(y0, y2);
2945 dir = -1;
2946 }
2947 if (y < y0 || y > y2) {
2948 return 0;
2949 }
caryclark9cb5d752015-12-18 04:35:24 -08002950 if (checkOnCurve(x, y, pts[0], pts[2])) {
2951 *onCurveCount += 1;
2952 return 0;
2953 }
caryclark9aacd902015-12-14 08:38:09 -08002954 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002955 return 0;
2956 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 // bounds check on X (not required. is it faster?)
2958#if 0
2959 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2960 return 0;
2961 }
2962#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002963
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 SkScalar roots[2];
2965 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2966 2 * (pts[1].fY - pts[0].fY),
2967 pts[0].fY - y,
2968 roots);
2969 SkASSERT(n <= 1);
2970 SkScalar xt;
2971 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002972 // zero roots are returned only when y0 == y
2973 // Need [0] if dir == 1
2974 // and [2] if dir == -1
2975 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 } else {
2977 SkScalar t = roots[0];
2978 SkScalar C = pts[0].fX;
2979 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2980 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002981 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002982 }
caryclark9aacd902015-12-14 08:38:09 -08002983 if (SkScalarNearlyEqual(xt, x)) {
2984 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2985 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002986 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002987 }
2988 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 return xt < x ? dir : 0;
2990}
2991
caryclark9aacd902015-12-14 08:38:09 -08002992static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 SkPoint dst[5];
2994 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002995
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2997 n = SkChopQuadAtYExtrema(pts, dst);
2998 pts = dst;
2999 }
caryclark9aacd902015-12-14 08:38:09 -08003000 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003002 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 }
3004 return w;
3005}
3006
caryclark9aacd902015-12-14 08:38:09 -08003007static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 SkScalar x0 = pts[0].fX;
3009 SkScalar y0 = pts[0].fY;
3010 SkScalar x1 = pts[1].fX;
3011 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003012
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003014
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 int dir = 1;
3016 if (y0 > y1) {
3017 SkTSwap(y0, y1);
3018 dir = -1;
3019 }
caryclark9aacd902015-12-14 08:38:09 -08003020 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003021 return 0;
3022 }
caryclark9cb5d752015-12-18 04:35:24 -08003023 if (checkOnCurve(x, y, pts[0], pts[1])) {
3024 *onCurveCount += 1;
3025 return 0;
3026 }
3027 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003028 return 0;
3029 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003030 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003031
caryclark9aacd902015-12-14 08:38:09 -08003032 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003033 // zero cross means the point is on the line, and since the case where
3034 // y of the query point is at the end point is handled above, we can be
3035 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003036 if (x != x1 || y != pts[1].fY) {
3037 *onCurveCount += 1;
3038 }
caryclark9aacd902015-12-14 08:38:09 -08003039 dir = 0;
3040 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003041 dir = 0;
3042 }
3043 return dir;
3044}
3045
caryclark9aacd902015-12-14 08:38:09 -08003046static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3047 SkTDArray<SkVector>* tangents) {
3048 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3049 && !between(pts[2].fY, y, pts[3].fY)) {
3050 return;
3051 }
3052 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3053 && !between(pts[2].fX, x, pts[3].fX)) {
3054 return;
3055 }
3056 SkPoint dst[10];
3057 int n = SkChopCubicAtYExtrema(pts, dst);
3058 for (int i = 0; i <= n; ++i) {
3059 SkPoint* c = &dst[i * 3];
3060 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003061 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003062 continue;
mbarbella276e6332016-05-31 14:44:01 -07003063 }
caryclark9aacd902015-12-14 08:38:09 -08003064 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3065 if (!SkScalarNearlyEqual(x, xt)) {
3066 continue;
3067 }
3068 SkVector tangent;
3069 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3070 tangents->push(tangent);
3071 }
3072}
3073
3074static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3075 SkTDArray<SkVector>* tangents) {
3076 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3077 return;
3078 }
3079 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3080 return;
3081 }
3082 SkScalar roots[2];
3083 SkScalar A = pts[2].fY;
3084 SkScalar B = pts[1].fY * w - y * w + y;
3085 SkScalar C = pts[0].fY;
3086 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3087 B -= C; // B = b*w - w * yCept + yCept - a
3088 C -= y;
3089 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3090 for (int index = 0; index < n; ++index) {
3091 SkScalar t = roots[index];
3092 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3093 if (!SkScalarNearlyEqual(x, xt)) {
3094 continue;
3095 }
3096 SkConic conic(pts, w);
3097 tangents->push(conic.evalTangentAt(t));
3098 }
3099}
3100
3101static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3102 SkTDArray<SkVector>* tangents) {
3103 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3104 return;
3105 }
3106 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3107 return;
3108 }
3109 SkScalar roots[2];
3110 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3111 2 * (pts[1].fY - pts[0].fY),
3112 pts[0].fY - y,
3113 roots);
3114 for (int index = 0; index < n; ++index) {
3115 SkScalar t = roots[index];
3116 SkScalar C = pts[0].fX;
3117 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3118 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003119 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003120 if (!SkScalarNearlyEqual(x, xt)) {
3121 continue;
3122 }
3123 tangents->push(SkEvalQuadTangentAt(pts, t));
3124 }
3125}
3126
3127static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3128 SkTDArray<SkVector>* tangents) {
3129 SkScalar y0 = pts[0].fY;
3130 SkScalar y1 = pts[1].fY;
3131 if (!between(y0, y, y1)) {
3132 return;
3133 }
3134 SkScalar x0 = pts[0].fX;
3135 SkScalar x1 = pts[1].fX;
3136 if (!between(x0, x, x1)) {
3137 return;
3138 }
3139 SkScalar dx = x1 - x0;
3140 SkScalar dy = y1 - y0;
3141 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3142 return;
3143 }
3144 SkVector v;
3145 v.set(dx, dy);
3146 tangents->push(v);
3147}
3148
reed@google.com4db592c2013-10-30 17:39:43 +00003149static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3150 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3151}
3152
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003153bool SkPath::contains(SkScalar x, SkScalar y) const {
3154 bool isInverse = this->isInverseFillType();
3155 if (this->isEmpty()) {
3156 return isInverse;
3157 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003158
reed@google.com4db592c2013-10-30 17:39:43 +00003159 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160 return isInverse;
3161 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003162
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163 SkPath::Iter iter(*this, true);
3164 bool done = false;
3165 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003166 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003167 do {
3168 SkPoint pts[4];
3169 switch (iter.next(pts, false)) {
3170 case SkPath::kMove_Verb:
3171 case SkPath::kClose_Verb:
3172 break;
3173 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003174 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003175 break;
3176 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003177 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003178 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003179 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003180 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003181 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003182 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003183 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003184 break;
3185 case SkPath::kDone_Verb:
3186 done = true;
3187 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003188 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003189 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003190 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3191 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3192 if (evenOddFill) {
3193 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 }
caryclark9aacd902015-12-14 08:38:09 -08003195 if (w) {
3196 return !isInverse;
3197 }
3198 if (onCurveCount <= 1) {
3199 return SkToBool(onCurveCount) ^ isInverse;
3200 }
3201 if ((onCurveCount & 1) || evenOddFill) {
3202 return SkToBool(onCurveCount & 1) ^ isInverse;
3203 }
halcanary9d524f22016-03-29 09:03:52 -07003204 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003205 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3206 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003207 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003208 SkTDArray<SkVector> tangents;
3209 do {
3210 SkPoint pts[4];
3211 int oldCount = tangents.count();
3212 switch (iter.next(pts, false)) {
3213 case SkPath::kMove_Verb:
3214 case SkPath::kClose_Verb:
3215 break;
3216 case SkPath::kLine_Verb:
3217 tangent_line(pts, x, y, &tangents);
3218 break;
3219 case SkPath::kQuad_Verb:
3220 tangent_quad(pts, x, y, &tangents);
3221 break;
3222 case SkPath::kConic_Verb:
3223 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3224 break;
3225 case SkPath::kCubic_Verb:
3226 tangent_cubic(pts, x, y, &tangents);
3227 break;
3228 case SkPath::kDone_Verb:
3229 done = true;
3230 break;
3231 }
3232 if (tangents.count() > oldCount) {
3233 int last = tangents.count() - 1;
3234 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003235 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003236 tangents.remove(last);
3237 } else {
3238 for (int index = 0; index < last; ++index) {
3239 const SkVector& test = tangents[index];
3240 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003241 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3242 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003243 tangents.remove(last);
3244 tangents.removeShuffle(index);
3245 break;
3246 }
3247 }
3248 }
3249 }
3250 } while (!done);
3251 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003252}
fmalitaaa0df4e2015-12-01 09:13:23 -08003253
3254int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3255 SkScalar w, SkPoint pts[], int pow2) {
3256 const SkConic conic(p0, p1, p2, w);
3257 return conic.chopIntoQuadsPOW2(pts, pow2);
3258}
bsalomonedc743a2016-06-01 09:42:31 -07003259
3260bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3261 unsigned* start) {
3262 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3263 return false;
3264 }
3265 SkPath::RawIter iter(path);
3266 SkPoint verbPts[4];
3267 SkPath::Verb v;
3268 SkPoint rectPts[5];
3269 int rectPtCnt = 0;
3270 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3271 switch (v) {
3272 case SkPath::kMove_Verb:
3273 if (0 != rectPtCnt) {
3274 return false;
3275 }
3276 rectPts[0] = verbPts[0];
3277 ++rectPtCnt;
3278 break;
3279 case SkPath::kLine_Verb:
3280 if (5 == rectPtCnt) {
3281 return false;
3282 }
3283 rectPts[rectPtCnt] = verbPts[1];
3284 ++rectPtCnt;
3285 break;
3286 case SkPath::kClose_Verb:
3287 if (4 == rectPtCnt) {
3288 rectPts[4] = rectPts[0];
3289 rectPtCnt = 5;
3290 }
3291 break;
3292 default:
3293 return false;
3294 }
3295 }
3296 if (rectPtCnt < 5) {
3297 return false;
3298 }
3299 if (rectPts[0] != rectPts[4]) {
3300 return false;
3301 }
bsalomon057ae8a2016-07-24 05:37:26 -07003302 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3303 // and pts 1 and 2 the opposite vertical or horizontal edge).
3304 bool vec03IsVertical;
3305 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3306 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3307 // Make sure it has non-zero width and height
3308 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003309 return false;
3310 }
bsalomon057ae8a2016-07-24 05:37:26 -07003311 vec03IsVertical = true;
3312 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3313 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3314 // Make sure it has non-zero width and height
3315 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3316 return false;
3317 }
3318 vec03IsVertical = false;
3319 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003320 return false;
3321 }
bsalomon057ae8a2016-07-24 05:37:26 -07003322 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3323 // set if it is on the bottom edge.
3324 unsigned sortFlags =
3325 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3326 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3327 switch (sortFlags) {
3328 case 0b00:
3329 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3330 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3331 *start = 0;
3332 break;
3333 case 0b01:
3334 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3335 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3336 *start = 1;
3337 break;
3338 case 0b10:
3339 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3340 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3341 *start = 3;
3342 break;
3343 case 0b11:
3344 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3345 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3346 *start = 2;
3347 break;
bsalomonedc743a2016-06-01 09:42:31 -07003348 }
bsalomonedc743a2016-06-01 09:42:31 -07003349 return true;
3350}
bsalomon21af9ca2016-08-25 12:29:23 -07003351
Brian Salomone4949402018-04-26 15:22:04 -04003352bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3353 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3354 // This gets converted to an oval.
3355 return true;
3356 }
3357 if (useCenter) {
3358 // This is a pie wedge. It's convex if the angle is <= 180.
3359 return SkScalarAbs(sweepAngle) <= 180.f;
3360 }
3361 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3362 // to a secant, i.e. convex.
3363 return SkScalarAbs(sweepAngle) <= 360.f;
3364}
3365
bsalomon21af9ca2016-08-25 12:29:23 -07003366void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3367 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3368 SkASSERT(!oval.isEmpty());
3369 SkASSERT(sweepAngle);
3370
3371 path->reset();
3372 path->setIsVolatile(true);
3373 path->setFillType(SkPath::kWinding_FillType);
3374 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3375 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003376 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003377 return;
3378 }
3379 if (useCenter) {
3380 path->moveTo(oval.centerX(), oval.centerY());
3381 }
Brian Salomone4949402018-04-26 15:22:04 -04003382 auto firstDir =
3383 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3384 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003385 // Arc to mods at 360 and drawArc is not supposed to.
3386 bool forceMoveTo = !useCenter;
3387 while (sweepAngle <= -360.f) {
3388 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3389 startAngle -= 180.f;
3390 path->arcTo(oval, startAngle, -180.f, false);
3391 startAngle -= 180.f;
3392 forceMoveTo = false;
3393 sweepAngle += 360.f;
3394 }
3395 while (sweepAngle >= 360.f) {
3396 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3397 startAngle += 180.f;
3398 path->arcTo(oval, startAngle, 180.f, false);
3399 startAngle += 180.f;
3400 forceMoveTo = false;
3401 sweepAngle -= 360.f;
3402 }
3403 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3404 if (useCenter) {
3405 path->close();
3406 }
Brian Salomone4949402018-04-26 15:22:04 -04003407 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3408 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003409}
Mike Reed0d7dac82017-02-02 17:45:56 -08003410
3411///////////////////////////////////////////////////////////////////////////////////////////////////
3412#include "SkNx.h"
3413
3414static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3415 SkScalar ts[2];
3416 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3417 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3418 SkASSERT(n >= 0 && n <= 2);
3419 for (int i = 0; i < n; ++i) {
3420 extremas[i] = SkEvalQuadAt(src, ts[i]);
3421 }
3422 extremas[n] = src[2];
3423 return n + 1;
3424}
3425
3426static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3427 SkConic conic(src[0], src[1], src[2], w);
3428 SkScalar ts[2];
3429 int n = conic.findXExtrema(ts);
3430 n += conic.findYExtrema(&ts[n]);
3431 SkASSERT(n >= 0 && n <= 2);
3432 for (int i = 0; i < n; ++i) {
3433 extremas[i] = conic.evalAt(ts[i]);
3434 }
3435 extremas[n] = src[2];
3436 return n + 1;
3437}
3438
3439static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3440 SkScalar ts[4];
3441 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3442 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3443 SkASSERT(n >= 0 && n <= 4);
3444 for (int i = 0; i < n; ++i) {
3445 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3446 }
3447 extremas[n] = src[3];
3448 return n + 1;
3449}
3450
Mike Reed8d3196b2017-02-03 11:34:13 -05003451SkRect SkPath::computeTightBounds() const {
3452 if (0 == this->countVerbs()) {
3453 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003454 }
3455
Mike Reed8d3196b2017-02-03 11:34:13 -05003456 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3457 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003458 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003459
Mike Reed0d7dac82017-02-02 17:45:56 -08003460 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3461 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003462 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003463
3464 // initial with the first MoveTo, so we don't have to check inside the switch
3465 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003466 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003467 for (;;) {
3468 int count = 0;
3469 switch (iter.next(pts)) {
3470 case SkPath::kMove_Verb:
3471 extremas[0] = pts[0];
3472 count = 1;
3473 break;
3474 case SkPath::kLine_Verb:
3475 extremas[0] = pts[1];
3476 count = 1;
3477 break;
3478 case SkPath::kQuad_Verb:
3479 count = compute_quad_extremas(pts, extremas);
3480 break;
3481 case SkPath::kConic_Verb:
3482 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3483 break;
3484 case SkPath::kCubic_Verb:
3485 count = compute_cubic_extremas(pts, extremas);
3486 break;
3487 case SkPath::kClose_Verb:
3488 break;
3489 case SkPath::kDone_Verb:
3490 goto DONE;
3491 }
3492 for (int i = 0; i < count; ++i) {
3493 Sk2s tmp = from_point(extremas[i]);
3494 min = Sk2s::Min(min, tmp);
3495 max = Sk2s::Max(max, tmp);
3496 }
3497 }
3498DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003499 SkRect bounds;
3500 min.store((SkPoint*)&bounds.fLeft);
3501 max.store((SkPoint*)&bounds.fRight);
3502 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003503}
Cary Clarkdf429f32017-11-08 11:44:31 -05003504
3505bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3506 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3507}
3508
3509bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3510 const SkPoint& p3, bool exact) {
3511 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3512 SkPointPriv::EqualsWithinTolerance(p2, p3);
3513}
3514
3515bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3516 const SkPoint& p3, const SkPoint& p4, bool exact) {
3517 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3518 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3519 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3520 SkPointPriv::EqualsWithinTolerance(p3, p4);
3521}