blob: d852cb68f5f998edbf4e8c01a3bb822fa7bc0cc2 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Hal Canaryc640d0d2018-06-13 09:59:02 -04008#include "SkPath.h"
9
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080011#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040012#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080013#include "SkGeometry.h"
Hal Canary22be4c42018-06-12 12:37:31 -040014#include "SkMacros.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040016#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070017#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000018#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050019#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000020#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050021#include "SkSafeMath.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040022#include "SkTo.h"
23
24#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040025#include <utility>
reed@android.com8a1c16f2008-12-17 15:59:43 +000026
Mike Reeda99b6ce2017-02-04 11:04:26 -050027static float poly_eval(float A, float B, float C, float t) {
28 return (A * t + B) * t + C;
29}
30
31static float poly_eval(float A, float B, float C, float D, float t) {
32 return ((A * t + B) * t + C) * t + D;
33}
34
reed@android.com8a1c16f2008-12-17 15:59:43 +000035////////////////////////////////////////////////////////////////////////////
36
reed@google.com3563c9e2011-11-14 19:34:57 +000037/**
38 * Path.bounds is defined to be the bounds of all the control points.
39 * If we called bounds.join(r) we would skip r if r was empty, which breaks
40 * our promise. Hence we have a custom joiner that doesn't look at emptiness
41 */
42static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
43 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
44 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
45 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
46 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
47}
48
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000049static bool is_degenerate(const SkPath& path) {
50 SkPath::Iter iter(path, false);
51 SkPoint pts[4];
52 return SkPath::kDone_Verb == iter.next(pts);
53}
54
bsalomon@google.com30c174b2012-11-13 14:36:42 +000055class SkAutoDisableDirectionCheck {
56public:
57 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070058 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000059 }
60
61 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070062 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000063 }
64
65private:
reed026beb52015-06-10 14:23:15 -070066 SkPath* fPath;
67 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000068};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000069#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000070
reed@android.com8a1c16f2008-12-17 15:59:43 +000071/* This guy's constructor/destructor bracket a path editing operation. It is
72 used when we know the bounds of the amount we are going to add to the path
73 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000074
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000076 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000077 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000078
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000079 It also notes if the path was originally degenerate, and if so, sets
80 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000081 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 */
83class SkAutoPathBoundsUpdate {
84public:
85 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
86 this->init(path);
87 }
88
89 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
90 SkScalar right, SkScalar bottom) {
91 fRect.set(left, top, right, bottom);
92 this->init(path);
93 }
reed@google.comabf15c12011-01-18 20:35:51 +000094
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000096 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
97 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050098 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 }
101 }
reed@google.comabf15c12011-01-18 20:35:51 +0000102
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 SkPath* fPath;
105 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000107 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000108 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000109
reed@android.com6b82d1a2009-06-03 02:35:01 +0000110 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000111 // Cannot use fRect for our bounds unless we know it is sorted
112 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000114 // Mark the path's bounds as dirty if (1) they are, or (2) the path
115 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000116 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000118 if (fHasValidBounds && !fEmpty) {
119 joinNoEmptyChecks(&fRect, fPath->getBounds());
120 }
121 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 }
123};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000124#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126////////////////////////////////////////////////////////////////////////////
127
128/*
129 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000130 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
132
133 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000134 1. If we encounter degenerate segments, remove them
135 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
136 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
137 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138*/
139
140////////////////////////////////////////////////////////////////////////////
141
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000142// flag to require a moveTo if we begin with something else, like lineTo etc.
143#define INITIAL_LASTMOVETOINDEX_VALUE ~0
144
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000145SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800146 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700148 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400149 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150}
151
152void SkPath::resetFields() {
153 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000154 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000156 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700157 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000158
159 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700160 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000161}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162
bungeman@google.coma5809a32013-06-21 15:13:34 +0000163SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000164 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 this->copyFields(that);
166 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167}
168
169SkPath::~SkPath() {
170 SkDEBUGCODE(this->validate();)
171}
172
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173SkPath& SkPath::operator=(const SkPath& that) {
174 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 if (this != &that) {
177 fPathRef.reset(SkRef(that.fPathRef.get()));
178 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 }
180 SkDEBUGCODE(this->validate();)
181 return *this;
182}
183
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184void SkPath::copyFields(const SkPath& that) {
185 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000186 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700188 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400189 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400190
191 // Non-atomic assignment of atomic values.
192 fConvexity .store(that.fConvexity .load());
193 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194}
195
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000196bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000197 // note: don't need to look at isConvex or bounds, since just comparing the
198 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000199 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000200 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000201}
202
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 if (this != &that) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400205 using std::swap;
bungeman77a53de2015-10-01 12:28:49 -0700206 fPathRef.swap(that.fPathRef);
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400207 swap(fLastMoveToIndex, that.fLastMoveToIndex);
208 swap(fFillType, that.fFillType);
209 swap(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400210
211 // Non-atomic swaps of atomic values.
212 Convexity c = fConvexity.load();
213 fConvexity.store(that.fConvexity.load());
214 that.fConvexity.store(c);
215
216 uint8_t fd = fFirstDirection.load();
217 fFirstDirection.store(that.fFirstDirection.load());
218 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 }
220}
221
caryclark8e7b19d2016-02-18 04:11:48 -0800222bool SkPath::isInterpolatable(const SkPath& compare) const {
223 int count = fPathRef->countVerbs();
224 if (count != compare.fPathRef->countVerbs()) {
225 return false;
226 }
227 if (!count) {
228 return true;
229 }
230 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
231 count)) {
232 return false;
233 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800234 return !fPathRef->countWeights() ||
235 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800236 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
237}
238
239bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500240 int pointCount = fPathRef->countPoints();
241 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800242 return false;
243 }
Cary Clark7487ec82018-03-06 09:30:46 -0500244 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800245 return true;
246 }
caryclark8e7b19d2016-02-18 04:11:48 -0800247 out->reset();
248 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700249 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800250 return true;
251}
252
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253static inline bool check_edge_against_rect(const SkPoint& p0,
254 const SkPoint& p1,
255 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700256 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 const SkPoint* edgeBegin;
258 SkVector v;
reed026beb52015-06-10 14:23:15 -0700259 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 v = p1 - p0;
261 edgeBegin = &p0;
262 } else {
263 v = p0 - p1;
264 edgeBegin = &p1;
265 }
266 if (v.fX || v.fY) {
267 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500268 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
269 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
270 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
271 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
273 return false;
274 }
275 }
276 return true;
277}
278
279bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
280 // This only handles non-degenerate convex paths currently.
281 if (kConvex_Convexity != this->getConvexity()) {
282 return false;
283 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000284
reed026beb52015-06-10 14:23:15 -0700285 SkPathPriv::FirstDirection direction;
286 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 return false;
288 }
289
290 SkPoint firstPt;
291 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 SkPath::Verb verb;
294 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400295 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000296 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298
Lee Salzmana19f0242017-01-12 13:06:21 -0500299 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 int nextPt = -1;
301 switch (verb) {
302 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 SkDEBUGCODE(++moveCnt);
305 firstPt = prevPt = pts[0];
306 break;
307 case kLine_Verb:
308 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000309 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400310 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311 break;
312 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000313 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000314 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400315 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000316 nextPt = 2;
317 break;
318 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000319 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400320 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000321 nextPt = 3;
322 break;
323 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000324 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000325 break;
326 default:
327 SkDEBUGFAIL("unknown verb");
328 }
329 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800330 if (SkPath::kConic_Verb == verb) {
331 SkConic orig;
332 orig.set(pts, iter.conicWeight());
333 SkPoint quadPts[5];
334 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800335 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800336
337 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
338 return false;
339 }
340 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
341 return false;
342 }
343 } else {
344 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
345 return false;
346 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000347 }
348 prevPt = pts[nextPt];
349 }
350 }
351
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400352 if (segmentCount) {
353 return check_edge_against_rect(prevPt, firstPt, rect, direction);
354 }
355 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000356}
357
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000358uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000359 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800360#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400361 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
362 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000363#endif
364 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000365}
djsollen@google.come63793a2012-03-21 15:39:03 +0000366
Mike Reedb6317422018-08-15 10:23:39 -0400367SkPath& SkPath::reset() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400372 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373}
374
Mike Reedb6317422018-08-15 10:23:39 -0400375SkPath& SkPath::rewind() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376 SkDEBUGCODE(this->validate();)
377
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000378 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000379 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400380 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381}
382
fsb1475b02016-01-20 09:51:07 -0800383bool SkPath::isLastContourClosed() const {
384 int verbCount = fPathRef->countVerbs();
385 if (0 == verbCount) {
386 return false;
387 }
388 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
389}
390
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000392 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000393
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000394 if (2 == verbCount) {
395 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
396 if (kLine_Verb == fPathRef->atVerb(1)) {
397 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000398 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000399 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000400 line[0] = pts[0];
401 line[1] = pts[1];
402 }
403 return true;
404 }
405 }
406 return false;
407}
408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409/*
410 Determines if path is a rect by keeping track of changes in direction
411 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000412
caryclark@google.comf1316942011-07-26 19:54:45 +0000413 The direction is computed such that:
414 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000415 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000416 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000417 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000418
caryclark@google.comf1316942011-07-26 19:54:45 +0000419A rectangle cycles up/right/down/left or up/left/down/right.
420
421The test fails if:
422 The path is closed, and followed by a line.
423 A second move creates a new endpoint.
424 A diagonal line is parsed.
425 There's more than four changes of direction.
426 There's a discontinuity on the line (e.g., a move in the middle)
427 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 The path contains a quadratic or cubic.
429 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000430 *The rectangle doesn't complete a cycle.
431 *The final point isn't equal to the first point.
432
433 *These last two conditions we relax if we have a 3-edge path that would
434 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000435
caryclark@google.comf1316942011-07-26 19:54:45 +0000436It's OK if the path has:
437 Several colinear line segments composing a rectangle side.
438 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000439
caryclark@google.comf1316942011-07-26 19:54:45 +0000440The direction takes advantage of the corners found since opposite sides
441must travel in opposite directions.
442
443FIXME: Allow colinear quads and cubics to be treated like lines.
444FIXME: If the API passes fill-only, return true if the filled stroke
445 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446
Cary Clark48c464a2018-04-16 12:06:07 -0400447 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000448 0x1 is set if the segment is horizontal
449 0x2 is set if the segment is moving to the right or down
450 thus:
451 two directions are opposites iff (dirA ^ dirB) == 0x2
452 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000453
caryclark@google.comf1316942011-07-26 19:54:45 +0000454 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000455static int rect_make_dir(SkScalar dx, SkScalar dy) {
456 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
457}
Cary Clark88ba9712018-04-12 14:00:24 -0400458
caryclark@google.comf68154a2012-11-21 15:18:06 +0000459bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400460 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400462 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400463 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400464 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
465 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400466 SkPoint firstCorner;
467 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000468 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400469 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400470 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400471 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000472 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700474 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000475 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000476 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700477 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
478 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000480 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700482 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000483 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400484 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400485 lastPt = pts;
486 }
Cary Clark88ba9712018-04-12 14:00:24 -0400487 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
488 SkVector lineDelta = lineEnd - lineStart;
489 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 return false; // diagonal
491 }
Cary Clark1eece782018-04-20 10:14:41 -0400492 if (!lineDelta.isFinite()) {
493 return false; // path contains infinity or NaN
494 }
Cary Clark88ba9712018-04-12 14:00:24 -0400495 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 break; // single point on side OK
497 }
Cary Clark48c464a2018-04-16 12:06:07 -0400498 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000499 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400500 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000501 corners = 1;
502 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400503 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 break;
505 }
506 if (closedOrMoved) {
507 return false; // closed followed by a line
508 }
Cary Clark48c464a2018-04-16 12:06:07 -0400509 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000510 break; // colinear with first
511 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000512 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400513 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400514 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400515 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400516 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400517 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 break; // colinear segment
519 }
Cary Clark48c464a2018-04-16 12:06:07 -0400520 directions[corners++] = nextDirection;
521 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400522 switch (corners) {
523 case 2:
524 firstCorner = lineStart;
525 break;
526 case 3:
527 if ((directions[0] ^ directions[2]) != 2) {
528 return false;
529 }
530 thirdCorner = lineEnd;
531 break;
532 case 4:
533 if ((directions[1] ^ directions[3]) != 2) {
534 return false;
535 }
536 break;
537 default:
538 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000539 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400540 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000541 break;
542 }
543 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000544 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000545 case kCubic_Verb:
546 return false; // quadratic, cubic not allowed
547 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400548 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700549 insertClose = true;
550 *currVerb -= 1; // try move again afterwards
551 goto addMissingClose;
552 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400553 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400554 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400555 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400556 closeXY = *firstPt - *lastPt;
557 if (closeXY.fX && closeXY.fY) {
558 return false; // we're diagonal, abort
559 }
Cary Clark8540e112018-04-11 14:30:27 -0400560 }
Cary Clark88ba9712018-04-12 14:00:24 -0400561 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000562 closedOrMoved = true;
563 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000564 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000565 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000566 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700569addMissingClose:
570 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000571 }
572 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400573 if (corners < 3 || corners > 4) {
574 return false;
575 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000576 if (savePts) {
577 *ptsPtr = savePts;
578 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400579 // check if close generates diagonal
580 closeXY = *firstPt - *lastPt;
581 if (closeXY.fX && closeXY.fY) {
582 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400583 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400584 if (rect) {
585 rect->set(firstCorner, thirdCorner);
586 }
587 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000588 *isClosed = autoClose;
589 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400590 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400591 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000592 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400593 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000594}
595
robertphillips4f662e62014-12-29 14:06:51 -0800596bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000597 SkDEBUGCODE(this->validate();)
598 int currVerb = 0;
599 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400600 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000601}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000602
caryclark95bc5f32015-04-08 08:34:15 -0700603bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 SkDEBUGCODE(this->validate();)
605 int currVerb = 0;
606 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400608 SkRect testRects[2];
609 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return false;
611 }
Cary Clark5c715182018-04-09 16:07:11 -0400612 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 if (testRects[0].contains(testRects[1])) {
614 if (rects) {
615 rects[0] = testRects[0];
616 rects[1] = testRects[1];
617 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000618 if (dirs) {
619 dirs[0] = testDirs[0];
620 dirs[1] = testDirs[1];
621 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000622 return true;
623 }
624 if (testRects[1].contains(testRects[0])) {
625 if (rects) {
626 rects[0] = testRects[1];
627 rects[1] = testRects[0];
628 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000629 if (dirs) {
630 dirs[0] = testDirs[1];
631 dirs[1] = testDirs[0];
632 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000633 return true;
634 }
635 }
636 return false;
637}
638
Mike Reed0c3137c2018-02-20 13:57:05 -0500639bool SkPath::isOval(SkRect* bounds) const {
640 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
641}
642
643bool SkPath::isRRect(SkRRect* rrect) const {
644 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
645}
646
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647int SkPath::countPoints() const {
648 return fPathRef->countPoints();
649}
650
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 SkDEBUGCODE(this->validate();)
653
654 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000655 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800657 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659}
660
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000661SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
663 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000664 }
665 return SkPoint::Make(0, 0);
666}
667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668int SkPath::countVerbs() const {
669 return fPathRef->countVerbs();
670}
671
672static inline void copy_verbs_reverse(uint8_t* inorderDst,
673 const uint8_t* reversedSrc,
674 int count) {
675 for (int i = 0; i < count; ++i) {
676 inorderDst[i] = reversedSrc[~i];
677 }
678}
679
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000680int SkPath::getVerbs(uint8_t dst[], int max) const {
681 SkDEBUGCODE(this->validate();)
682
683 SkASSERT(max >= 0);
684 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 int count = SkMin32(max, fPathRef->countVerbs());
686 copy_verbs_reverse(dst, fPathRef->verbs(), count);
687 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000688}
689
reed@google.com294dd7b2011-10-11 11:58:32 +0000690bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 SkDEBUGCODE(this->validate();)
692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 if (count > 0) {
695 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000696 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000698 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000700 if (lastPt) {
701 lastPt->set(0, 0);
702 }
703 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704}
705
caryclarkaec25102015-04-29 08:28:30 -0700706void SkPath::setPt(int index, SkScalar x, SkScalar y) {
707 SkDEBUGCODE(this->validate();)
708
709 int count = fPathRef->countPoints();
710 if (count <= index) {
711 return;
712 } else {
713 SkPathRef::Editor ed(&fPathRef);
714 ed.atPoint(index)->set(x, y);
715 }
716}
717
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718void SkPath::setLastPt(SkScalar x, SkScalar y) {
719 SkDEBUGCODE(this->validate();)
720
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 if (count == 0) {
723 this->moveTo(x, y);
724 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 SkPathRef::Editor ed(&fPathRef);
726 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 }
728}
729
reed@google.com04863fa2011-05-15 04:08:24 +0000730void SkPath::setConvexity(Convexity c) {
731 if (fConvexity != c) {
732 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000733 }
734}
735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736//////////////////////////////////////////////////////////////////////////////
737// Construction methods
738
reed026beb52015-06-10 14:23:15 -0700739#define DIRTY_AFTER_EDIT \
740 do { \
741 fConvexity = kUnknown_Convexity; \
742 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000743 } while (0)
744
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745void SkPath::incReserve(U16CPU inc) {
746 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748 SkDEBUGCODE(this->validate();)
749}
750
Mike Reedb6317422018-08-15 10:23:39 -0400751SkPath& SkPath::moveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752 SkDEBUGCODE(this->validate();)
753
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000754 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000756 // remember our index
757 fLastMoveToIndex = fPathRef->countPoints();
758
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000759 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700760
761 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400762 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763}
764
Mike Reedb6317422018-08-15 10:23:39 -0400765SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 SkPoint pt;
767 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400768 return this->moveTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769}
770
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000771void SkPath::injectMoveToIfNeeded() {
772 if (fLastMoveToIndex < 0) {
773 SkScalar x, y;
774 if (fPathRef->countVerbs() == 0) {
775 x = y = 0;
776 } else {
777 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
778 x = pt.fX;
779 y = pt.fY;
780 }
781 this->moveTo(x, y);
782 }
783}
784
Mike Reedb6317422018-08-15 10:23:39 -0400785SkPath& SkPath::lineTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 SkDEBUGCODE(this->validate();)
787
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000788 this->injectMoveToIfNeeded();
789
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000790 SkPathRef::Editor ed(&fPathRef);
791 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792
reed@google.comb54455e2011-05-16 14:16:04 +0000793 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400794 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795}
796
Mike Reedb6317422018-08-15 10:23:39 -0400797SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000798 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799 SkPoint pt;
800 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400801 return this->lineTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802}
803
Mike Reedb6317422018-08-15 10:23:39 -0400804SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000806
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000807 this->injectMoveToIfNeeded();
808
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000809 SkPathRef::Editor ed(&fPathRef);
810 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 pts[0].set(x1, y1);
812 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000813
reed@google.comb54455e2011-05-16 14:16:04 +0000814 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400815 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816}
817
Mike Reedb6317422018-08-15 10:23:39 -0400818SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000819 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 SkPoint pt;
821 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400822 return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823}
824
Mike Reedb6317422018-08-15 10:23:39 -0400825SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
826 SkScalar w) {
reed@google.com277c3f82013-05-31 15:17:50 +0000827 // check for <= 0 or NaN with this test
828 if (!(w > 0)) {
829 this->lineTo(x2, y2);
830 } else if (!SkScalarIsFinite(w)) {
831 this->lineTo(x1, y1);
832 this->lineTo(x2, y2);
833 } else if (SK_Scalar1 == w) {
834 this->quadTo(x1, y1, x2, y2);
835 } else {
836 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000837
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000838 this->injectMoveToIfNeeded();
839
reed@google.com277c3f82013-05-31 15:17:50 +0000840 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000841 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000842 pts[0].set(x1, y1);
843 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000844
reed@google.com277c3f82013-05-31 15:17:50 +0000845 DIRTY_AFTER_EDIT;
846 }
Mike Reedb6317422018-08-15 10:23:39 -0400847 return *this;
reed@google.com277c3f82013-05-31 15:17:50 +0000848}
849
Mike Reedb6317422018-08-15 10:23:39 -0400850SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
851 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000852 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000853 SkPoint pt;
854 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400855 return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000856}
857
Mike Reedb6317422018-08-15 10:23:39 -0400858SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
859 SkScalar x3, SkScalar y3) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkDEBUGCODE(this->validate();)
861
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000862 this->injectMoveToIfNeeded();
863
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000864 SkPathRef::Editor ed(&fPathRef);
865 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866 pts[0].set(x1, y1);
867 pts[1].set(x2, y2);
868 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869
reed@google.comb54455e2011-05-16 14:16:04 +0000870 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400871 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872}
873
Mike Reedb6317422018-08-15 10:23:39 -0400874SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
875 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000876 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 SkPoint pt;
878 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400879 return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
880 pt.fX + x3, pt.fY + y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881}
882
Mike Reedb6317422018-08-15 10:23:39 -0400883SkPath& SkPath::close() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 SkDEBUGCODE(this->validate();)
885
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000886 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 case kLine_Verb:
890 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000891 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000893 case kMove_Verb: {
894 SkPathRef::Editor ed(&fPathRef);
895 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000897 }
reed@google.com277c3f82013-05-31 15:17:50 +0000898 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000899 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000900 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000901 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000902 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000903 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 }
905 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000906
907 // signal that we need a moveTo to follow us (unless we're done)
908#if 0
909 if (fLastMoveToIndex >= 0) {
910 fLastMoveToIndex = ~fLastMoveToIndex;
911 }
912#else
913 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
914#endif
Mike Reedb6317422018-08-15 10:23:39 -0400915 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916}
917
918///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000919
fmalitac08d53e2015-11-17 09:53:29 -0800920namespace {
921
922template <unsigned N>
923class PointIterator {
924public:
925 PointIterator(SkPath::Direction dir, unsigned startIndex)
926 : fCurrent(startIndex % N)
927 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
928
929 const SkPoint& current() const {
930 SkASSERT(fCurrent < N);
931 return fPts[fCurrent];
932 }
933
934 const SkPoint& next() {
935 fCurrent = (fCurrent + fAdvance) % N;
936 return this->current();
937 }
938
939protected:
940 SkPoint fPts[N];
941
942private:
943 unsigned fCurrent;
944 unsigned fAdvance;
945};
946
947class RectPointIterator : public PointIterator<4> {
948public:
949 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
950 : PointIterator(dir, startIndex) {
951
952 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
953 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
954 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
955 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
956 }
957};
958
959class OvalPointIterator : public PointIterator<4> {
960public:
961 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
962 : PointIterator(dir, startIndex) {
963
964 const SkScalar cx = oval.centerX();
965 const SkScalar cy = oval.centerY();
966
967 fPts[0] = SkPoint::Make(cx, oval.fTop);
968 fPts[1] = SkPoint::Make(oval.fRight, cy);
969 fPts[2] = SkPoint::Make(cx, oval.fBottom);
970 fPts[3] = SkPoint::Make(oval.fLeft, cy);
971 }
972};
973
974class RRectPointIterator : public PointIterator<8> {
975public:
976 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
977 : PointIterator(dir, startIndex) {
978
979 const SkRect& bounds = rrect.getBounds();
980 const SkScalar L = bounds.fLeft;
981 const SkScalar T = bounds.fTop;
982 const SkScalar R = bounds.fRight;
983 const SkScalar B = bounds.fBottom;
984
985 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
986 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
987 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
988 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
989 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
990 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
991 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
992 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
993 }
994};
995
996} // anonymous namespace
997
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000998static void assert_known_direction(int dir) {
999 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
1000}
1001
Mike Reedb6317422018-08-15 10:23:39 -04001002SkPath& SkPath::addRect(const SkRect& rect, Direction dir) {
1003 return this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004}
1005
Mike Reedb6317422018-08-15 10:23:39 -04001006SkPath& SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 SkScalar bottom, Direction dir) {
Mike Reedb6317422018-08-15 10:23:39 -04001008 return this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
fmalitac08d53e2015-11-17 09:53:29 -08001009}
1010
Mike Reedb6317422018-08-15 10:23:39 -04001011SkPath& SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001012 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001013 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001014 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001015 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001016 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001017
fmalitac08d53e2015-11-17 09:53:29 -08001018 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019
fmalitac08d53e2015-11-17 09:53:29 -08001020 const int kVerbs = 5; // moveTo + 3x lineTo + close
1021 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022
fmalitac08d53e2015-11-17 09:53:29 -08001023 RectPointIterator iter(rect, dir, startIndex);
1024
1025 this->moveTo(iter.current());
1026 this->lineTo(iter.next());
1027 this->lineTo(iter.next());
1028 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001030
1031 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
Mike Reedb6317422018-08-15 10:23:39 -04001032 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033}
1034
Mike Reedb6317422018-08-15 10:23:39 -04001035SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) {
reed@google.com744faba2012-05-29 19:54:52 +00001036 SkDEBUGCODE(this->validate();)
1037 if (count <= 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001038 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
1040
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001041 fLastMoveToIndex = fPathRef->countPoints();
1042
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001043 // +close makes room for the extra kClose_Verb
1044 SkPathRef::Editor ed(&fPathRef, count+close, count);
1045
1046 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001047 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001048 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1049 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001050 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001051
reed@google.com744faba2012-05-29 19:54:52 +00001052 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001053 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001054 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001055 }
1056
reed@google.com744faba2012-05-29 19:54:52 +00001057 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001058 SkDEBUGCODE(this->validate();)
Mike Reedb6317422018-08-15 10:23:39 -04001059 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001060}
1061
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062#include "SkGeometry.h"
1063
reedf90ea012015-01-29 12:03:58 -08001064static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1065 SkPoint* pt) {
1066 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001067 // Chrome uses this path to move into and out of ovals. If not
1068 // treated as a special case the moves can distort the oval's
1069 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001070 pt->set(oval.fRight, oval.centerY());
1071 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072 } else if (0 == oval.width() && 0 == oval.height()) {
1073 // Chrome will sometimes create 0 radius round rects. Having degenerate
1074 // quad segments in the path prevents the path from being recognized as
1075 // a rect.
1076 // TODO: optimizing the case where only one of width or height is zero
1077 // should also be considered. This case, however, doesn't seem to be
1078 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001079 pt->set(oval.fRight, oval.fTop);
1080 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081 }
reedf90ea012015-01-29 12:03:58 -08001082 return false;
1083}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084
reedd5d27d92015-02-09 13:54:43 -08001085// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1086//
1087static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1088 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1089 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1090 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091
1092 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001093 loss in radians-conversion and/or sin/cos, we may end up with coincident
1094 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1095 of drawing a nearly complete circle (good).
1096 e.g. canvas.drawArc(0, 359.99, ...)
1097 -vs- canvas.drawArc(0, 359.9, ...)
1098 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099 */
reedd5d27d92015-02-09 13:54:43 -08001100 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001101 SkScalar sw = SkScalarAbs(sweepAngle);
1102 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1103 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1104 // make a guess at a tiny angle (in radians) to tweak by
1105 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1106 // not sure how much will be enough, so we use a loop
1107 do {
1108 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001109 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1110 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001111 }
1112 }
reedd5d27d92015-02-09 13:54:43 -08001113 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1114}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001115
reed9e779d42015-02-17 11:43:14 -08001116/**
1117 * If this returns 0, then the caller should just line-to the singlePt, else it should
1118 * ignore singlePt and append the specified number of conics.
1119 */
reedd5d27d92015-02-09 13:54:43 -08001120static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001121 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1122 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001123 SkMatrix matrix;
1124
1125 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1126 matrix.postTranslate(oval.centerX(), oval.centerY());
1127
reed9e779d42015-02-17 11:43:14 -08001128 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1129 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001130 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001131 }
1132 return count;
reedd5d27d92015-02-09 13:54:43 -08001133}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001134
Mike Reedb6317422018-08-15 10:23:39 -04001135SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001136 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001137 SkRRect rrect;
1138 rrect.setRectRadii(rect, (const SkVector*) radii);
Mike Reedb6317422018-08-15 10:23:39 -04001139 return this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140}
1141
Mike Reedb6317422018-08-15 10:23:39 -04001142SkPath& SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001143 // legacy start indices: 6 (CW) and 7(CCW)
Mike Reedb6317422018-08-15 10:23:39 -04001144 return this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
fmalitac08d53e2015-11-17 09:53:29 -08001145}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001146
Mike Reedb6317422018-08-15 10:23:39 -04001147SkPath& SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1148 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001149
Mike Reedb6317422018-08-15 10:23:39 -04001150 bool isRRect = hasOnlyMoveTos();
1151 const SkRect& bounds = rrect.getBounds();
fmalitac08d53e2015-11-17 09:53:29 -08001152
Mike Reedb6317422018-08-15 10:23:39 -04001153 if (rrect.isRect() || rrect.isEmpty()) {
1154 // degenerate(rect) => radii points are collapsing
1155 this->addRect(bounds, dir, (startIndex + 1) / 2);
1156 } else if (rrect.isOval()) {
1157 // degenerate(oval) => line points are collapsing
1158 this->addOval(bounds, dir, startIndex / 2);
1159 } else {
1160 fFirstDirection = this->hasOnlyMoveTos() ?
1161 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
fmalitac08d53e2015-11-17 09:53:29 -08001162
Mike Reedb6317422018-08-15 10:23:39 -04001163 SkAutoPathBoundsUpdate apbu(this, bounds);
1164 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001165
Mike Reedb6317422018-08-15 10:23:39 -04001166 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1167 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1168 const SkScalar weight = SK_ScalarRoot2Over2;
fmalitac08d53e2015-11-17 09:53:29 -08001169
Mike Reedb6317422018-08-15 10:23:39 -04001170 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1171 const int kVerbs = startsWithConic
1172 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1173 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1174 this->incReserve(kVerbs);
fmalitac08d53e2015-11-17 09:53:29 -08001175
Mike Reedb6317422018-08-15 10:23:39 -04001176 RRectPointIterator rrectIter(rrect, dir, startIndex);
1177 // Corner iterator indices follow the collapsed radii model,
1178 // adjusted such that the start pt is "behind" the radii start pt.
1179 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1180 RectPointIterator rectIter(bounds, dir, rectStartIndex);
fmalitac08d53e2015-11-17 09:53:29 -08001181
Mike Reedb6317422018-08-15 10:23:39 -04001182 this->moveTo(rrectIter.current());
1183 if (startsWithConic) {
1184 for (unsigned i = 0; i < 3; ++i) {
fmalitac08d53e2015-11-17 09:53:29 -08001185 this->conicTo(rectIter.next(), rrectIter.next(), weight);
Mike Reedb6317422018-08-15 10:23:39 -04001186 this->lineTo(rrectIter.next());
fmalitac08d53e2015-11-17 09:53:29 -08001187 }
Mike Reedb6317422018-08-15 10:23:39 -04001188 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1189 // final lineTo handled by close().
1190 } else {
1191 for (unsigned i = 0; i < 4; ++i) {
1192 this->lineTo(rrectIter.next());
1193 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1194 }
fmalitac08d53e2015-11-17 09:53:29 -08001195 }
Mike Reedb6317422018-08-15 10:23:39 -04001196 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001197
Mike Reedb6317422018-08-15 10:23:39 -04001198 SkPathRef::Editor ed(&fPathRef);
1199 ed.setIsRRect(isRRect, dir, startIndex % 8);
1200
1201 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1202 }
1203
1204 SkDEBUGCODE(fPathRef->validate();)
1205 return *this;
reed@google.com4ed0fb72012-12-12 20:48:18 +00001206}
1207
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001208bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001209 int count = fPathRef->countVerbs();
1210 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1211 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001212 if (*verbs == kLine_Verb ||
1213 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001214 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001215 *verbs == kCubic_Verb) {
1216 return false;
1217 }
1218 ++verbs;
1219 }
1220 return true;
1221}
1222
Brian Osmana2318572017-07-10 15:09:26 -04001223bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1224 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001225 if (count < 2) {
1226 return true;
1227 }
Brian Osmana2318572017-07-10 15:09:26 -04001228 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001229 const SkPoint& first = *pts;
1230 for (int index = 1; index < count; ++index) {
1231 if (first != pts[index]) {
1232 return false;
1233 }
1234 }
1235 return true;
1236}
1237
Mike Reedb6317422018-08-15 10:23:39 -04001238SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1239 Direction dir) {
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001240 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001241
humper@google.com75e3ca12013-04-08 21:44:11 +00001242 if (rx < 0 || ry < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001243 return *this;
humper@google.com75e3ca12013-04-08 21:44:11 +00001244 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001245
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001246 SkRRect rrect;
1247 rrect.setRectXY(rect, rx, ry);
Mike Reedb6317422018-08-15 10:23:39 -04001248 return this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001249}
1250
Mike Reedb6317422018-08-15 10:23:39 -04001251SkPath& SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001252 // legacy start index: 1
Mike Reedb6317422018-08-15 10:23:39 -04001253 return this->addOval(oval, dir, 1);
fmalitac08d53e2015-11-17 09:53:29 -08001254}
1255
Mike Reedb6317422018-08-15 10:23:39 -04001256SkPath& SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001257 assert_known_direction(dir);
1258
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001259 /* If addOval() is called after previous moveTo(),
1260 this path is still marked as an oval. This is used to
1261 fit into WebKit's calling sequences.
1262 We can't simply check isEmpty() in this case, as additional
1263 moveTo() would mark the path non empty.
1264 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001265 bool isOval = hasOnlyMoveTos();
1266 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001267 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001268 } else {
reed026beb52015-06-10 14:23:15 -07001269 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001270 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001271
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001272 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001273 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274
fmalitac08d53e2015-11-17 09:53:29 -08001275 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1276 const int kVerbs = 6; // moveTo + 4x conicTo + close
1277 this->incReserve(kVerbs);
1278
1279 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1280 // The corner iterator pts are tracking "behind" the oval/radii pts.
1281 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001282 const SkScalar weight = SK_ScalarRoot2Over2;
1283
fmalitac08d53e2015-11-17 09:53:29 -08001284 this->moveTo(ovalIter.current());
1285 for (unsigned i = 0; i < 4; ++i) {
1286 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001287 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289
fmalitac08d53e2015-11-17 09:53:29 -08001290 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1291
robertphillips@google.com466310d2013-12-03 16:43:54 +00001292 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001293
bsalomon78d58d12016-05-27 09:17:04 -07001294 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
Mike Reedb6317422018-08-15 10:23:39 -04001295 return *this;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001296}
1297
Mike Reedb6317422018-08-15 10:23:39 -04001298SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001300 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 }
Mike Reedb6317422018-08-15 10:23:39 -04001302 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303}
1304
Mike Reedb6317422018-08-15 10:23:39 -04001305SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1306 bool forceMoveTo) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307 if (oval.width() < 0 || oval.height() < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001308 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 }
1310
reedf90ea012015-01-29 12:03:58 -08001311 if (fPathRef->countVerbs() == 0) {
1312 forceMoveTo = true;
1313 }
1314
1315 SkPoint lonePt;
1316 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
Mike Reedb6317422018-08-15 10:23:39 -04001317 return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
reedf90ea012015-01-29 12:03:58 -08001318 }
1319
reedd5d27d92015-02-09 13:54:43 -08001320 SkVector startV, stopV;
1321 SkRotationDirection dir;
1322 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1323
reed9e779d42015-02-17 11:43:14 -08001324 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001325
Brian Salomone4949402018-04-26 15:22:04 -04001326 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1327 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1328 // arcs from the same oval.
1329 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1330 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001331 if (forceMoveTo) {
1332 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001333 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001334 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1335 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1336 this->lineTo(pt);
1337 }
1338 };
1339
xidachen6069dda2016-10-06 05:42:23 -07001340 // At this point, we know that the arc is not a lone point, but startV == stopV
1341 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1342 // cannot handle it.
1343 if (startV == stopV) {
1344 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1345 SkScalar radiusX = oval.width() / 2;
1346 SkScalar radiusY = oval.height() / 2;
1347 // We cannot use SkScalarSinCos function in the next line because
1348 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1349 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001350 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001351 // make sin(endAngle) to be 0 which will then draw a dot.
1352 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1353 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001354 addPt(singlePt);
Mike Reedb6317422018-08-15 10:23:39 -04001355 return *this;
xidachen6069dda2016-10-06 05:42:23 -07001356 }
1357
reedd5d27d92015-02-09 13:54:43 -08001358 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001359 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001360 if (count) {
1361 this->incReserve(count * 2 + 1);
1362 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001363 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001364 for (int i = 0; i < count; ++i) {
1365 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1366 }
reed9e779d42015-02-17 11:43:14 -08001367 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001368 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001369 }
Mike Reedb6317422018-08-15 10:23:39 -04001370 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371}
1372
caryclark55d49052016-01-23 05:07:04 -08001373// This converts the SVG arc to conics.
1374// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1375// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1376// See also SVG implementation notes:
1377// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1378// Note that arcSweep bool value is flipped from the original implementation.
Mike Reedb6317422018-08-15 10:23:39 -04001379SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1380 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001381 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001382 SkPoint srcPts[2];
1383 this->getLastPt(&srcPts[0]);
1384 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1385 // joining the endpoints.
1386 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1387 if (!rx || !ry) {
Mike Reedb6317422018-08-15 10:23:39 -04001388 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001389 }
1390 // If the current point and target point for the arc are identical, it should be treated as a
1391 // zero length path. This ensures continuity in animations.
1392 srcPts[1].set(x, y);
1393 if (srcPts[0] == srcPts[1]) {
Mike Reedb6317422018-08-15 10:23:39 -04001394 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001395 }
1396 rx = SkScalarAbs(rx);
1397 ry = SkScalarAbs(ry);
1398 SkVector midPointDistance = srcPts[0] - srcPts[1];
1399 midPointDistance *= 0.5f;
1400
1401 SkMatrix pointTransform;
1402 pointTransform.setRotate(-angle);
1403
1404 SkPoint transformedMidPoint;
1405 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1406 SkScalar squareRx = rx * rx;
1407 SkScalar squareRy = ry * ry;
1408 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1409 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1410
1411 // Check if the radii are big enough to draw the arc, scale radii if not.
1412 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1413 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1414 if (radiiScale > 1) {
1415 radiiScale = SkScalarSqrt(radiiScale);
1416 rx *= radiiScale;
1417 ry *= radiiScale;
1418 }
1419
1420 pointTransform.setScale(1 / rx, 1 / ry);
1421 pointTransform.preRotate(-angle);
1422
1423 SkPoint unitPts[2];
1424 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1425 SkVector delta = unitPts[1] - unitPts[0];
1426
1427 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1428 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1429
1430 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1431 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1432 scaleFactor = -scaleFactor;
1433 }
1434 delta.scale(scaleFactor);
1435 SkPoint centerPoint = unitPts[0] + unitPts[1];
1436 centerPoint *= 0.5f;
1437 centerPoint.offset(-delta.fY, delta.fX);
1438 unitPts[0] -= centerPoint;
1439 unitPts[1] -= centerPoint;
1440 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1441 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1442 SkScalar thetaArc = theta2 - theta1;
1443 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1444 thetaArc += SK_ScalarPI * 2;
1445 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1446 thetaArc -= SK_ScalarPI * 2;
1447 }
1448 pointTransform.setRotate(angle);
1449 pointTransform.preScale(rx, ry);
1450
Cary Clark1acd3c72017-12-08 11:37:01 -05001451#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001452 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001453#else
1454 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1455 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1456#endif
caryclark55d49052016-01-23 05:07:04 -08001457 SkScalar thetaWidth = thetaArc / segments;
1458 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1459 if (!SkScalarIsFinite(t)) {
Mike Reedb6317422018-08-15 10:23:39 -04001460 return *this;
caryclark55d49052016-01-23 05:07:04 -08001461 }
1462 SkScalar startTheta = theta1;
1463 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001464#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1465 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1466 return scalar == SkScalarFloorToScalar(scalar);
1467 };
1468 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1469 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1470 scalar_is_integer(x) && scalar_is_integer(y);
1471#endif
caryclark55d49052016-01-23 05:07:04 -08001472 for (int i = 0; i < segments; ++i) {
1473 SkScalar endTheta = startTheta + thetaWidth;
1474 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1475
1476 unitPts[1].set(cosEndTheta, sinEndTheta);
1477 unitPts[1] += centerPoint;
1478 unitPts[0] = unitPts[1];
1479 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1480 SkPoint mapped[2];
1481 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001482 /*
1483 Computing the arc width introduces rounding errors that cause arcs to start
1484 outside their marks. A round rect may lose convexity as a result. If the input
1485 values are on integers, place the conic on integers as well.
1486 */
1487#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1488 if (expectIntegers) {
1489 SkScalar* mappedScalars = &mapped[0].fX;
1490 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1491 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1492 }
1493 }
1494#endif
caryclark55d49052016-01-23 05:07:04 -08001495 this->conicTo(mapped[0], mapped[1], w);
1496 startTheta = endTheta;
1497 }
Mike Reedb6317422018-08-15 10:23:39 -04001498 return *this;
caryclark55d49052016-01-23 05:07:04 -08001499}
1500
Mike Reedb6317422018-08-15 10:23:39 -04001501SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1502 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
caryclark55d49052016-01-23 05:07:04 -08001503 SkPoint currentPoint;
1504 this->getLastPt(&currentPoint);
Mike Reedb6317422018-08-15 10:23:39 -04001505 return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
1506 currentPoint.fX + dx, currentPoint.fY + dy);
caryclark55d49052016-01-23 05:07:04 -08001507}
1508
Mike Reedb6317422018-08-15 10:23:39 -04001509SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 if (oval.isEmpty() || 0 == sweepAngle) {
Mike Reedb6317422018-08-15 10:23:39 -04001511 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 }
1513
1514 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1515
1516 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001517 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1518 // See SkPath::addOval() docs.
1519 SkScalar startOver90 = startAngle / 90.f;
1520 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1521 SkScalar error = startOver90 - startOver90I;
1522 if (SkScalarNearlyEqual(error, 0)) {
1523 // Index 1 is at startAngle == 0.
1524 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1525 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
Mike Reedb6317422018-08-15 10:23:39 -04001526 return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1527 (unsigned) startIndex);
bsalomon1978ce22016-05-31 10:42:16 -07001528 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 }
Mike Reedb6317422018-08-15 10:23:39 -04001530 return this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531}
1532
1533/*
1534 Need to handle the case when the angle is sharp, and our computed end-points
1535 for the arc go behind pt1 and/or p2...
1536*/
Mike Reedb6317422018-08-15 10:23:39 -04001537SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001538 if (radius == 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001539 return this->lineTo(x1, y1);
reeda8b326c2014-12-09 11:50:32 -08001540 }
1541
1542 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001543
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 // need to know our prev pt so we can construct tangent vectors
1545 {
1546 SkPoint start;
1547 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001548 // Handle degenerate cases by adding a line to the first point and
1549 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 before.setNormalize(x1 - start.fX, y1 - start.fY);
1551 after.setNormalize(x2 - x1, y2 - y1);
1552 }
reed@google.comabf15c12011-01-18 20:35:51 +00001553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 SkScalar cosh = SkPoint::DotProduct(before, after);
1555 SkScalar sinh = SkPoint::CrossProduct(before, after);
1556
1557 if (SkScalarNearlyZero(sinh)) { // angle is too tight
Mike Reedb6317422018-08-15 10:23:39 -04001558 return this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 }
reed@google.comabf15c12011-01-18 20:35:51 +00001560
Mike Reeda99b6ce2017-02-04 11:04:26 -05001561 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562
Mike Reeda99b6ce2017-02-04 11:04:26 -05001563 SkScalar xx = x1 - dist * before.fX;
1564 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001565 after.setLength(dist);
1566 this->lineTo(xx, yy);
1567 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
Mike Reedb6317422018-08-15 10:23:39 -04001568 return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569}
1570
1571///////////////////////////////////////////////////////////////////////////////
1572
Mike Reedb6317422018-08-15 10:23:39 -04001573SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 SkMatrix matrix;
1575
1576 matrix.setTranslate(dx, dy);
Mike Reedb6317422018-08-15 10:23:39 -04001577 return this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578}
1579
Mike Reedb6317422018-08-15 10:23:39 -04001580SkPath& SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001581 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001582
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001583 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 SkPoint pts[4];
1585 Verb verb;
1586
Cary Clark9480d822017-10-19 18:01:13 -04001587 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001588 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 while ((verb = iter.next(pts)) != kDone_Verb) {
1590 switch (verb) {
1591 case kMove_Verb:
1592 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001593 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1594 injectMoveToIfNeeded(); // In case last contour is closed
1595 this->lineTo(pts[0]);
1596 } else {
1597 this->moveTo(pts[0]);
1598 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 break;
1600 case kLine_Verb:
1601 proc(matrix, &pts[1], &pts[1], 1);
1602 this->lineTo(pts[1]);
1603 break;
1604 case kQuad_Verb:
1605 proc(matrix, &pts[1], &pts[1], 2);
1606 this->quadTo(pts[1], pts[2]);
1607 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001608 case kConic_Verb:
1609 proc(matrix, &pts[1], &pts[1], 2);
1610 this->conicTo(pts[1], pts[2], iter.conicWeight());
1611 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 case kCubic_Verb:
1613 proc(matrix, &pts[1], &pts[1], 3);
1614 this->cubicTo(pts[1], pts[2], pts[3]);
1615 break;
1616 case kClose_Verb:
1617 this->close();
1618 break;
1619 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001620 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001622 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 }
Mike Reedb6317422018-08-15 10:23:39 -04001624 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625}
1626
1627///////////////////////////////////////////////////////////////////////////////
1628
reed@google.com277c3f82013-05-31 15:17:50 +00001629static int pts_in_verb(unsigned verb) {
1630 static const uint8_t gPtsInVerb[] = {
1631 1, // kMove
1632 1, // kLine
1633 2, // kQuad
1634 2, // kConic
1635 3, // kCubic
1636 0, // kClose
1637 0 // kDone
1638 };
1639
1640 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1641 return gPtsInVerb[verb];
1642}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644// ignore the last point of the 1st contour
Mike Reedb6317422018-08-15 10:23:39 -04001645SkPath& SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001646 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1647 if (!verbs) { // empty path returns nullptr
Mike Reedb6317422018-08-15 10:23:39 -04001648 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 }
caryclark51c56782016-11-07 05:09:28 -08001650 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1651 SkASSERT(verbsEnd[0] == kMove_Verb);
1652 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1653 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654
caryclark51c56782016-11-07 05:09:28 -08001655 while (verbs < verbsEnd) {
1656 uint8_t v = *verbs++;
1657 pts -= pts_in_verb(v);
1658 switch (v) {
1659 case kMove_Verb:
1660 // if the path has multiple contours, stop after reversing the last
Mike Reedb6317422018-08-15 10:23:39 -04001661 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001663 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 break;
1665 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001666 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001668 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001669 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001670 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001672 this->cubicTo(pts[2], pts[1], pts[0]);
1673 break;
1674 case kClose_Verb:
1675 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 break;
1677 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001678 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 break;
1680 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 }
Mike Reedb6317422018-08-15 10:23:39 -04001682 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683}
1684
Mike Reedb6317422018-08-15 10:23:39 -04001685SkPath& SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001686 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 const SkPoint* pts = src.fPathRef->pointsEnd();
1689 // we will iterator through src's verbs backwards
1690 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1691 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001692 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001693
1694 bool needMove = true;
1695 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001696 while (verbs < verbsEnd) {
1697 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001698 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001699
1700 if (needMove) {
1701 --pts;
1702 this->moveTo(pts->fX, pts->fY);
1703 needMove = false;
1704 }
1705 pts -= n;
1706 switch (v) {
1707 case kMove_Verb:
1708 if (needClose) {
1709 this->close();
1710 needClose = false;
1711 }
1712 needMove = true;
1713 pts += 1; // so we see the point in "if (needMove)" above
1714 break;
1715 case kLine_Verb:
1716 this->lineTo(pts[0]);
1717 break;
1718 case kQuad_Verb:
1719 this->quadTo(pts[1], pts[0]);
1720 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001721 case kConic_Verb:
1722 this->conicTo(pts[1], pts[0], *--conicWeights);
1723 break;
reed@google.com63d73742012-01-10 15:33:12 +00001724 case kCubic_Verb:
1725 this->cubicTo(pts[2], pts[1], pts[0]);
1726 break;
1727 case kClose_Verb:
1728 needClose = true;
1729 break;
1730 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001731 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001732 }
1733 }
Mike Reedb6317422018-08-15 10:23:39 -04001734 return *this;
reed@google.com63d73742012-01-10 15:33:12 +00001735}
1736
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737///////////////////////////////////////////////////////////////////////////////
1738
1739void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1740 SkMatrix matrix;
1741
1742 matrix.setTranslate(dx, dy);
1743 this->transform(matrix, dst);
1744}
1745
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1747 int level = 2) {
1748 if (--level >= 0) {
1749 SkPoint tmp[7];
1750
1751 SkChopCubicAtHalf(pts, tmp);
1752 subdivide_cubic_to(path, &tmp[0], level);
1753 subdivide_cubic_to(path, &tmp[3], level);
1754 } else {
1755 path->cubicTo(pts[1], pts[2], pts[3]);
1756 }
1757}
1758
1759void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1760 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001761 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 dst = (SkPath*)this;
1763 }
1764
tomhudson@google.com8d430182011-06-06 19:11:19 +00001765 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 SkPath tmp;
1767 tmp.fFillType = fFillType;
1768
1769 SkPath::Iter iter(*this, false);
1770 SkPoint pts[4];
1771 SkPath::Verb verb;
1772
reed@google.com4a3b7142012-05-16 17:16:46 +00001773 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 switch (verb) {
1775 case kMove_Verb:
1776 tmp.moveTo(pts[0]);
1777 break;
1778 case kLine_Verb:
1779 tmp.lineTo(pts[1]);
1780 break;
1781 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001782 // promote the quad to a conic
1783 tmp.conicTo(pts[1], pts[2],
1784 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001786 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001787 tmp.conicTo(pts[1], pts[2],
1788 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001789 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 case kCubic_Verb:
1791 subdivide_cubic_to(&tmp, pts);
1792 break;
1793 case kClose_Verb:
1794 tmp.close();
1795 break;
1796 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001797 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 break;
1799 }
1800 }
1801
1802 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001803 SkPathRef::Editor ed(&dst->fPathRef);
1804 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001805 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1808
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001811 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001812 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001814
reed026beb52015-06-10 14:23:15 -07001815 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1816 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001817 } else {
1818 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001819 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1820 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001821 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001822 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1823 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001824 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001825 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001826 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001827 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001828 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001829 }
1830 }
1831
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 SkDEBUGCODE(dst->validate();)
1833 }
1834}
1835
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836///////////////////////////////////////////////////////////////////////////////
1837///////////////////////////////////////////////////////////////////////////////
1838
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839SkPath::Iter::Iter() {
1840#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001841 fPts = nullptr;
1842 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001845 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846#endif
1847 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001848 fVerbs = nullptr;
1849 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 fNeedClose = false;
1851}
1852
1853SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1854 this->setPath(path, forceClose);
1855}
1856
1857void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001858 fPts = path.fPathRef->points();
1859 fVerbs = path.fPathRef->verbs();
1860 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001861 fConicWeights = path.fPathRef->conicWeights();
1862 if (fConicWeights) {
1863 fConicWeights -= 1; // begin one behind
1864 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001866 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 fForceClose = SkToU8(forceClose);
1868 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001869 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870}
1871
1872bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001873 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 return false;
1875 }
1876 if (fForceClose) {
1877 return true;
1878 }
1879
1880 const uint8_t* verbs = fVerbs;
1881 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001882
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 if (kMove_Verb == *(verbs - 1)) {
1884 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 }
1886
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001887 while (verbs > stop) {
1888 // verbs points one beyond the current verb, decrement first.
1889 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 if (kMove_Verb == v) {
1891 break;
1892 }
1893 if (kClose_Verb == v) {
1894 return true;
1895 }
1896 }
1897 return false;
1898}
1899
1900SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001903 // A special case: if both points are NaN, SkPoint::operation== returns
1904 // false, but the iterator expects that they are treated as the same.
1905 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001906 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1907 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001908 return kClose_Verb;
1909 }
1910
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911 pts[0] = fLastPt;
1912 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 fLastPt = fMoveTo;
1914 fCloseLine = true;
1915 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001916 } else {
1917 pts[0] = fMoveTo;
1918 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920}
1921
reed@google.com9e25dbf2012-05-15 17:05:38 +00001922const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 if (fSegmentState == kAfterMove_SegmentState) {
1924 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001926 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001927 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001928
1929 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1930 // Set the first return pt to the last pt of the previous primitive.
1931 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932}
1933
caryclarke8c56662015-07-14 11:19:26 -07001934void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 // We need to step over anything that will not move the current draw point
1936 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001937 const uint8_t* lastMoveVerb = nullptr;
1938 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001939 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 SkPoint lastPt = fLastPt;
1941 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 switch (verb) {
1944 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 // Keep a record of this most recent move
1946 lastMoveVerb = fVerbs;
1947 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001948 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001949 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fPts++;
1952 break;
1953
1954 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001955 // A close when we are in a segment is always valid except when it
1956 // follows a move which follows a segment.
1957 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 return;
1959 }
1960 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001961 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 break;
1963
1964 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001965 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 if (lastMoveVerb) {
1967 fVerbs = lastMoveVerb;
1968 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001969 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 return;
1971 }
1972 return;
1973 }
1974 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 fPts++;
1977 break;
1978
reed@google.com277c3f82013-05-31 15:17:50 +00001979 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001981 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 if (lastMoveVerb) {
1983 fVerbs = lastMoveVerb;
1984 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001985 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 return;
1987 }
1988 return;
1989 }
1990 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001991 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001993 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994 break;
1995
1996 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001997 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 if (lastMoveVerb) {
1999 fVerbs = lastMoveVerb;
2000 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002001 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 return;
2003 }
2004 return;
2005 }
2006 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002007 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002008 fPts += 3;
2009 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002010
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002011 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002012 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002013 }
2014 }
2015}
2016
reed@google.com4a3b7142012-05-16 17:16:46 +00002017SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002018 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002019
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002021 // Close the curve if requested and if there is some curve to close
2022 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002023 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 return kLine_Verb;
2025 }
2026 fNeedClose = false;
2027 return kClose_Verb;
2028 }
2029 return kDone_Verb;
2030 }
2031
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002032 // fVerbs is one beyond the current verb, decrement first
2033 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002034 const SkPoint* SK_RESTRICT srcPts = fPts;
2035 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036
2037 switch (verb) {
2038 case kMove_Verb:
2039 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002040 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 verb = this->autoClose(pts);
2042 if (verb == kClose_Verb) {
2043 fNeedClose = false;
2044 }
2045 return (Verb)verb;
2046 }
2047 if (fVerbs == fVerbStop) { // might be a trailing moveto
2048 return kDone_Verb;
2049 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002050 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002051 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002053 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002054 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 fNeedClose = fForceClose;
2056 break;
2057 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002058 pts[0] = this->cons_moveTo();
2059 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 fLastPt = srcPts[0];
2061 fCloseLine = false;
2062 srcPts += 1;
2063 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002064 case kConic_Verb:
2065 fConicWeights += 1;
2066 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002068 pts[0] = this->cons_moveTo();
2069 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 fLastPt = srcPts[1];
2071 srcPts += 2;
2072 break;
2073 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002074 pts[0] = this->cons_moveTo();
2075 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076 fLastPt = srcPts[2];
2077 srcPts += 3;
2078 break;
2079 case kClose_Verb:
2080 verb = this->autoClose(pts);
2081 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002082 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083 } else {
2084 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002085 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002087 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 break;
2089 }
2090 fPts = srcPts;
2091 return (Verb)verb;
2092}
2093
2094///////////////////////////////////////////////////////////////////////////////
2095
Ben Wagner4d1955c2017-03-10 13:08:15 -05002096#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002097#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002098#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002099
reed@google.com51bbe752013-01-17 22:07:50 +00002100static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002101 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002102 str->append(label);
2103 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002104
reed@google.com51bbe752013-01-17 22:07:50 +00002105 const SkScalar* values = &pts[0].fX;
2106 count *= 2;
2107
2108 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002109 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002110 if (i < count - 1) {
2111 str->append(", ");
2112 }
2113 }
Mike Reed405b9d22018-01-09 15:11:08 -05002114 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002115 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002116 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002117 }
caryclark08fa28c2014-10-23 13:08:56 -07002118 str->append(");");
reede05fed02014-12-15 07:59:53 -08002119 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002120 str->append(" // ");
2121 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002122 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002123 if (i < count - 1) {
2124 str->append(", ");
2125 }
2126 }
2127 if (conicWeight >= 0) {
2128 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002129 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002130 }
2131 }
2132 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002133}
2134
caryclarke9562592014-09-15 09:26:09 -07002135void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002136 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 Iter iter(*this, forceClose);
2138 SkPoint pts[4];
2139 Verb verb;
2140
reed@google.com51bbe752013-01-17 22:07:50 +00002141 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002142 char const * const gFillTypeStrs[] = {
2143 "Winding",
2144 "EvenOdd",
2145 "InverseWinding",
2146 "InverseEvenOdd",
2147 };
2148 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2149 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002150 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 switch (verb) {
2152 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002153 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
2155 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002156 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 break;
2158 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002159 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002161 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002162 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002163 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002165 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002168 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 break;
2170 default:
2171 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2172 verb = kDone_Verb; // stop the loop
2173 break;
2174 }
caryclark1049f122015-04-20 08:31:59 -07002175 if (!wStream && builder.size()) {
2176 SkDebugf("%s", builder.c_str());
2177 builder.reset();
2178 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 }
caryclark66a5d8b2014-06-24 08:30:15 -07002180 if (wStream) {
2181 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002182 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183}
2184
reed@android.come522ca52009-11-23 20:10:41 +00002185void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002186 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002187}
2188
2189void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002190 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002191}
2192
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002193
Cary Clark0461e9f2017-08-25 15:13:38 -04002194bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002195 if ((fFillType & ~3) != 0) {
2196 return false;
2197 }
reed@google.comabf15c12011-01-18 20:35:51 +00002198
djsollen@google.com077348c2012-10-22 20:23:32 +00002199#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002200 if (!fBoundsIsDirty) {
2201 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002202
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002203 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002204 if (SkToBool(fIsFinite) != isFinite) {
2205 return false;
2206 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002207
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002208 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002209 // if we're empty, fBounds may be empty but translated, so we can't
2210 // necessarily compare to bounds directly
2211 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2212 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002213 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2214 return false;
2215 }
reed@android.come522ca52009-11-23 20:10:41 +00002216 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002217 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002218 if (!fBounds.isEmpty()) {
2219 return false;
2220 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002221 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002222 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002223 if (!fBounds.contains(bounds)) {
2224 return false;
2225 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002226 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002227 }
reed@android.come522ca52009-11-23 20:10:41 +00002228 }
2229 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002230#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002231 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002232}
reed@android.come522ca52009-11-23 20:10:41 +00002233
reed@google.com04863fa2011-05-15 04:08:24 +00002234///////////////////////////////////////////////////////////////////////////////
2235
reed@google.com0b7b9822011-05-16 12:29:27 +00002236static int sign(SkScalar x) { return x < 0; }
2237#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002238
robertphillipsc506e302014-09-16 09:43:31 -07002239enum DirChange {
2240 kLeft_DirChange,
2241 kRight_DirChange,
2242 kStraight_DirChange,
2243 kBackwards_DirChange,
2244
2245 kInvalid_DirChange
2246};
2247
2248
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002249static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002250 // The error epsilon was empirically derived; worse case round rects
2251 // with a mid point outset by 2x float epsilon in tests had an error
2252 // of 12.
2253 const int epsilon = 16;
2254 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2255 return false;
2256 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002257 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002258 int aBits = SkFloatAs2sCompliment(compA);
2259 int bBits = SkFloatAs2sCompliment(compB);
2260 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002261}
2262
caryclarkb4216502015-03-02 13:02:34 -08002263static bool approximately_zero_when_compared_to(double x, double y) {
2264 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002265}
2266
caryclarkb4216502015-03-02 13:02:34 -08002267
reed@google.com04863fa2011-05-15 04:08:24 +00002268// only valid for a single contour
2269struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 Convexicator()
2271 : fPtCount(0)
2272 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002273 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002274 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002275 , fIsCurve(false)
2276 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002277 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002278 // warnings
djsollen2f124632016-04-29 13:53:05 -07002279 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002280 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002281 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002282 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002283 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002284
2285 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002286 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002287 }
2288
2289 SkPath::Convexity getConvexity() const { return fConvexity; }
2290
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002291 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002292 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002293
reed@google.com04863fa2011-05-15 04:08:24 +00002294 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002295 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002296 return;
2297 }
2298
2299 if (0 == fPtCount) {
2300 fCurrPt = pt;
2301 ++fPtCount;
2302 } else {
2303 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002304 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002305 if (!SkScalarIsFinite(lengthSqd)) {
2306 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002307 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002308 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002309 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002310 fCurrPt = pt;
2311 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002312 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002313 } else {
2314 SkASSERT(fPtCount > 2);
2315 this->addVec(vec);
2316 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002317
reed@google.com85b6e392011-05-15 20:25:17 +00002318 int sx = sign(vec.fX);
2319 int sy = sign(vec.fY);
2320 fDx += (sx != fSx);
2321 fDy += (sy != fSy);
2322 fSx = sx;
2323 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002324
reed@google.com85b6e392011-05-15 20:25:17 +00002325 if (fDx > 3 || fDy > 3) {
2326 fConvexity = SkPath::kConcave_Convexity;
2327 }
reed@google.com04863fa2011-05-15 04:08:24 +00002328 }
2329 }
2330 }
2331
2332 void close() {
2333 if (fPtCount > 2) {
2334 this->addVec(fFirstVec);
2335 }
2336 }
2337
caryclarkb4216502015-03-02 13:02:34 -08002338 DirChange directionChange(const SkVector& curVec) {
2339 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2340
2341 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2342 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2343 largest = SkTMax(largest, -smallest);
2344
2345 if (!almost_equal(largest, largest + cross)) {
2346 int sign = SkScalarSignAsInt(cross);
2347 if (sign) {
2348 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2349 }
2350 }
2351
2352 if (cross) {
2353 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2354 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2355 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2356 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2357 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2358 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2359 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2360 if (sign) {
2361 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2362 }
2363 }
2364 }
2365
Cary Clarkdf429f32017-11-08 11:44:31 -05002366 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2367 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2368 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2369 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002370 fLastVec.dot(curVec) < 0.0f) {
2371 return kBackwards_DirChange;
2372 }
2373
2374 return kStraight_DirChange;
2375 }
2376
Cary Clarkc8323aa2017-08-25 08:04:43 -04002377 bool hasBackwards() const {
2378 return fBackwards;
2379 }
caryclarkb4216502015-03-02 13:02:34 -08002380
caryclarkd3d1a982014-12-08 04:57:38 -08002381 bool isFinite() const {
2382 return fIsFinite;
2383 }
2384
caryclark5ccef572015-03-02 10:07:56 -08002385 void setCurve(bool isCurve) {
2386 fIsCurve = isCurve;
2387 }
2388
reed@google.com04863fa2011-05-15 04:08:24 +00002389private:
2390 void addVec(const SkVector& vec) {
2391 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002392 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002393 switch (dir) {
2394 case kLeft_DirChange: // fall through
2395 case kRight_DirChange:
2396 if (kInvalid_DirChange == fExpectedDir) {
2397 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002398 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2399 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002400 } else if (dir != fExpectedDir) {
2401 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002402 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002403 }
2404 fLastVec = vec;
2405 break;
2406 case kStraight_DirChange:
2407 break;
2408 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002409 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002410 // If any of the subsequent dir is non-backward, it'll be concave.
2411 // Otherwise, it's still convex.
2412 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002413 }
robertphillipsc506e302014-09-16 09:43:31 -07002414 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002415 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002416 break;
2417 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002418 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002419 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 }
2421 }
2422
caryclarkb4216502015-03-02 13:02:34 -08002423 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002424 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002425 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002426 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2427 // value with the current vec is deemed to be of a significant value.
2428 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002429 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002430 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002431 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002432 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002433 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002434 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002435 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002436 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002437};
2438
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002439SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002440 // Sometimes we think we need to calculate convexity but another thread already did.
2441 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2442 return c;
2443 }
2444
reed@google.com04863fa2011-05-15 04:08:24 +00002445 SkPoint pts[4];
2446 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002447 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002448
2449 int contourCount = 0;
2450 int count;
2451 Convexicator state;
2452
caryclarkd3d1a982014-12-08 04:57:38 -08002453 if (!isFinite()) {
2454 return kUnknown_Convexity;
2455 }
Brian Osman205a1262017-09-18 13:13:48 +00002456 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002457 switch (verb) {
2458 case kMove_Verb:
2459 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002461 return kConcave_Convexity;
2462 }
2463 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002464 // fall through
2465 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002466 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002467 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002468 break;
caryclark5ccef572015-03-02 10:07:56 -08002469 case kQuad_Verb:
2470 // fall through
2471 case kConic_Verb:
2472 // fall through
2473 case kCubic_Verb:
2474 count = 2 + (kCubic_Verb == verb);
2475 // As an additional enhancement, this could set curve true only
2476 // if the curve is nonlinear
2477 state.setCurve(true);
2478 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002479 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002480 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002481 state.close();
2482 count = 0;
2483 break;
2484 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002485 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002486 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002487 return kConcave_Convexity;
2488 }
2489
2490 for (int i = 1; i <= count; i++) {
2491 state.addPt(pts[i]);
2492 }
2493 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002494 if (!state.isFinite()) {
2495 return kUnknown_Convexity;
2496 }
reed@google.com04863fa2011-05-15 04:08:24 +00002497 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002498 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002499 return kConcave_Convexity;
2500 }
2501 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002502 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002503 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002504 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2505 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2506 fConvexity = Convexity::kConcave_Convexity;
2507 } else {
2508 fFirstDirection = state.getFirstDirection();
2509 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002510 }
2511 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002512}
reed@google.com69a99432012-01-10 18:00:10 +00002513
2514///////////////////////////////////////////////////////////////////////////////
2515
2516class ContourIter {
2517public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002518 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002519
2520 bool done() const { return fDone; }
2521 // if !done() then these may be called
2522 int count() const { return fCurrPtCount; }
2523 const SkPoint* pts() const { return fCurrPt; }
2524 void next();
2525
2526private:
2527 int fCurrPtCount;
2528 const SkPoint* fCurrPt;
2529 const uint8_t* fCurrVerb;
2530 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002531 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002532 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002533 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002534};
2535
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002536ContourIter::ContourIter(const SkPathRef& pathRef) {
2537 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002538 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002539 fCurrPt = pathRef.points();
2540 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002541 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002542 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002543 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002544 this->next();
2545}
2546
2547void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002548 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002549 fDone = true;
2550 }
2551 if (fDone) {
2552 return;
2553 }
2554
2555 // skip pts of prev contour
2556 fCurrPt += fCurrPtCount;
2557
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002558 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002559 int ptCount = 1; // moveTo
2560 const uint8_t* verbs = fCurrVerb;
2561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002562 for (--verbs; verbs > fStopVerbs; --verbs) {
2563 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002564 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002565 goto CONTOUR_END;
2566 case SkPath::kLine_Verb:
2567 ptCount += 1;
2568 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002569 case SkPath::kConic_Verb:
2570 fCurrConicWeight += 1;
2571 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002572 case SkPath::kQuad_Verb:
2573 ptCount += 2;
2574 break;
2575 case SkPath::kCubic_Verb:
2576 ptCount += 3;
2577 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002578 case SkPath::kClose_Verb:
2579 break;
2580 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002581 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002582 break;
2583 }
2584 }
2585CONTOUR_END:
2586 fCurrPtCount = ptCount;
2587 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002588 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002589}
2590
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002591// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002592static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002593 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2594 // We may get 0 when the above subtracts underflow. We expect this to be
2595 // very rare and lazily promote to double.
2596 if (0 == cross) {
2597 double p0x = SkScalarToDouble(p0.fX);
2598 double p0y = SkScalarToDouble(p0.fY);
2599
2600 double p1x = SkScalarToDouble(p1.fX);
2601 double p1y = SkScalarToDouble(p1.fY);
2602
2603 double p2x = SkScalarToDouble(p2.fX);
2604 double p2y = SkScalarToDouble(p2.fY);
2605
2606 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2607 (p1y - p0y) * (p2x - p0x));
2608
2609 }
2610 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002611}
2612
reed@google.comc1ea60a2012-01-31 15:15:36 +00002613// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002614static int find_max_y(const SkPoint pts[], int count) {
2615 SkASSERT(count > 0);
2616 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002617 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002618 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619 SkScalar y = pts[i].fY;
2620 if (y > max) {
2621 max = y;
2622 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002623 }
2624 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002625 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002626}
2627
reed@google.comcabaf1d2012-01-11 21:03:05 +00002628static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2629 int i = index;
2630 for (;;) {
2631 i = (i + inc) % n;
2632 if (i == index) { // we wrapped around, so abort
2633 break;
2634 }
2635 if (pts[index] != pts[i]) { // found a different point, success!
2636 break;
2637 }
2638 }
2639 return i;
2640}
2641
reed@google.comc1ea60a2012-01-31 15:15:36 +00002642/**
2643 * Starting at index, and moving forward (incrementing), find the xmin and
2644 * xmax of the contiguous points that have the same Y.
2645 */
2646static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2647 int* maxIndexPtr) {
2648 const SkScalar y = pts[index].fY;
2649 SkScalar min = pts[index].fX;
2650 SkScalar max = min;
2651 int minIndex = index;
2652 int maxIndex = index;
2653 for (int i = index + 1; i < n; ++i) {
2654 if (pts[i].fY != y) {
2655 break;
2656 }
2657 SkScalar x = pts[i].fX;
2658 if (x < min) {
2659 min = x;
2660 minIndex = i;
2661 } else if (x > max) {
2662 max = x;
2663 maxIndex = i;
2664 }
2665 }
2666 *maxIndexPtr = maxIndex;
2667 return minIndex;
2668}
2669
reed026beb52015-06-10 14:23:15 -07002670static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2671 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002672}
2673
reed@google.comac8543f2012-01-30 20:51:25 +00002674/*
2675 * We loop through all contours, and keep the computed cross-product of the
2676 * contour that contained the global y-max. If we just look at the first
2677 * contour, we may find one that is wound the opposite way (correctly) since
2678 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2679 * that is outer most (or at least has the global y-max) before we can consider
2680 * its cross product.
2681 */
reed026beb52015-06-10 14:23:15 -07002682bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002683 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2684 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002685 return true;
2686 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687
2688 // don't want to pay the cost for computing this if it
2689 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002690 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2691 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002692 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002693 return false;
2694 }
reed@google.com69a99432012-01-10 18:00:10 +00002695
reed026beb52015-06-10 14:23:15 -07002696 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002697
reed@google.comac8543f2012-01-30 20:51:25 +00002698 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002699 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002700 SkScalar ymaxCross = 0;
2701
reed@google.com69a99432012-01-10 18:00:10 +00002702 for (; !iter.done(); iter.next()) {
2703 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002704 if (n < 3) {
2705 continue;
2706 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002707
reed@google.comcabaf1d2012-01-11 21:03:05 +00002708 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002709 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002710 int index = find_max_y(pts, n);
2711 if (pts[index].fY < ymax) {
2712 continue;
2713 }
2714
2715 // If there is more than 1 distinct point at the y-max, we take the
2716 // x-min and x-max of them and just subtract to compute the dir.
2717 if (pts[(index + 1) % n].fY == pts[index].fY) {
2718 int maxIndex;
2719 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2720 if (minIndex == maxIndex) {
2721 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002722 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002723 SkASSERT(pts[minIndex].fY == pts[index].fY);
2724 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2725 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2726 // we just subtract the indices, and let that auto-convert to
2727 // SkScalar, since we just want - or + to signal the direction.
2728 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002729 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002730 TRY_CROSSPROD:
2731 // Find a next and prev index to use for the cross-product test,
2732 // but we try to find pts that form non-zero vectors from pts[index]
2733 //
2734 // Its possible that we can't find two non-degenerate vectors, so
2735 // we have to guard our search (e.g. all the pts could be in the
2736 // same place).
2737
2738 // we pass n - 1 instead of -1 so we don't foul up % operator by
2739 // passing it a negative LH argument.
2740 int prev = find_diff_pt(pts, index, n, n - 1);
2741 if (prev == index) {
2742 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002743 continue;
2744 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002745 int next = find_diff_pt(pts, index, n, 1);
2746 SkASSERT(next != index);
2747 cross = cross_prod(pts[prev], pts[index], pts[next]);
2748 // if we get a zero and the points are horizontal, then we look at the spread in
2749 // x-direction. We really should continue to walk away from the degeneracy until
2750 // there is a divergence.
2751 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2752 // construct the subtract so we get the correct Direction below
2753 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002754 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002755 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002756
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002757 if (cross) {
2758 // record our best guess so far
2759 ymax = pts[index].fY;
2760 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002761 }
2762 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002763 if (ymaxCross) {
2764 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002765 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002766 return true;
2767 } else {
2768 return false;
2769 }
reed@google.comac8543f2012-01-30 20:51:25 +00002770}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771
2772///////////////////////////////////////////////////////////////////////////////
2773
caryclark9aacd902015-12-14 08:38:09 -08002774static bool between(SkScalar a, SkScalar b, SkScalar c) {
2775 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2776 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2777 return (a - b) * (c - b) <= 0;
2778}
2779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2781 SkScalar t) {
2782 SkScalar A = c3 + 3*(c1 - c2) - c0;
2783 SkScalar B = 3*(c2 - c1 - c1 + c0);
2784 SkScalar C = 3*(c1 - c0);
2785 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002786 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787}
2788
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002789template <size_t N> static void find_minmax(const SkPoint pts[],
2790 SkScalar* minPtr, SkScalar* maxPtr) {
2791 SkScalar min, max;
2792 min = max = pts[0].fX;
2793 for (size_t i = 1; i < N; ++i) {
2794 min = SkMinScalar(min, pts[i].fX);
2795 max = SkMaxScalar(max, pts[i].fX);
2796 }
2797 *minPtr = min;
2798 *maxPtr = max;
2799}
2800
caryclark9cb5d752015-12-18 04:35:24 -08002801static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2802 if (start.fY == end.fY) {
2803 return between(start.fX, x, end.fX) && x != end.fX;
2804 } else {
2805 return x == start.fX && y == start.fY;
2806 }
2807}
2808
caryclark9aacd902015-12-14 08:38:09 -08002809static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002810 SkScalar y0 = pts[0].fY;
2811 SkScalar y3 = pts[3].fY;
2812
2813 int dir = 1;
2814 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002815 using std::swap;
2816 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002817 dir = -1;
2818 }
2819 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 return 0;
2821 }
caryclark9cb5d752015-12-18 04:35:24 -08002822 if (checkOnCurve(x, y, pts[0], pts[3])) {
2823 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002824 return 0;
2825 }
caryclark9cb5d752015-12-18 04:35:24 -08002826 if (y == y3) {
2827 return 0;
2828 }
2829
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 // quickreject or quickaccept
2831 SkScalar min, max;
2832 find_minmax<4>(pts, &min, &max);
2833 if (x < min) {
2834 return 0;
2835 }
2836 if (x > max) {
2837 return dir;
2838 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002839
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002841 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002842 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002843 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002844 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002845 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002846 if (SkScalarNearlyEqual(xt, x)) {
2847 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2848 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002849 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002850 }
2851 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 return xt < x ? dir : 0;
2853}
2854
caryclark9aacd902015-12-14 08:38:09 -08002855static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002856 SkPoint dst[10];
2857 int n = SkChopCubicAtYExtrema(pts, dst);
2858 int w = 0;
2859 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002860 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 }
2862 return w;
2863}
2864
caryclark9aacd902015-12-14 08:38:09 -08002865static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2866 SkASSERT(src);
2867 SkASSERT(t >= 0 && t <= 1);
2868 SkScalar src2w = src[2] * w;
2869 SkScalar C = src[0];
2870 SkScalar A = src[4] - 2 * src2w + C;
2871 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002872 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002873}
2874
2875
2876static double conic_eval_denominator(SkScalar w, SkScalar t) {
2877 SkScalar B = 2 * (w - 1);
2878 SkScalar C = 1;
2879 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002880 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002881}
2882
2883static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2884 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 SkScalar y0 = pts[0].fY;
2886 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 int dir = 1;
2889 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002890 using std::swap;
2891 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 dir = -1;
2893 }
caryclark9aacd902015-12-14 08:38:09 -08002894 if (y < y0 || y > y2) {
2895 return 0;
2896 }
caryclark9cb5d752015-12-18 04:35:24 -08002897 if (checkOnCurve(x, y, pts[0], pts[2])) {
2898 *onCurveCount += 1;
2899 return 0;
2900 }
caryclark9aacd902015-12-14 08:38:09 -08002901 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002902 return 0;
2903 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002904
caryclark9aacd902015-12-14 08:38:09 -08002905 SkScalar roots[2];
2906 SkScalar A = pts[2].fY;
2907 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2908 SkScalar C = pts[0].fY;
2909 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2910 B -= C; // B = b*w - w * yCept + yCept - a
2911 C -= y;
2912 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2913 SkASSERT(n <= 1);
2914 SkScalar xt;
2915 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002916 // zero roots are returned only when y0 == y
2917 // Need [0] if dir == 1
2918 // and [2] if dir == -1
2919 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002920 } else {
2921 SkScalar t = roots[0];
2922 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2923 }
2924 if (SkScalarNearlyEqual(xt, x)) {
2925 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2926 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002927 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002928 }
2929 }
2930 return xt < x ? dir : 0;
2931}
2932
2933static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2934 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2935 if (y0 == y1) {
2936 return true;
2937 }
2938 if (y0 < y1) {
2939 return y1 <= y2;
2940 } else {
2941 return y1 >= y2;
2942 }
2943}
2944
2945static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2946 int* onCurveCount) {
2947 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002948 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002949 // If the data points are very large, the conic may not be monotonic but may also
2950 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002951 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2952 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2953 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002954 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2955 }
2956 return w;
2957}
2958
2959static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2960 SkScalar y0 = pts[0].fY;
2961 SkScalar y2 = pts[2].fY;
2962
2963 int dir = 1;
2964 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002965 using std::swap;
2966 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08002967 dir = -1;
2968 }
2969 if (y < y0 || y > y2) {
2970 return 0;
2971 }
caryclark9cb5d752015-12-18 04:35:24 -08002972 if (checkOnCurve(x, y, pts[0], pts[2])) {
2973 *onCurveCount += 1;
2974 return 0;
2975 }
caryclark9aacd902015-12-14 08:38:09 -08002976 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002977 return 0;
2978 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002979 // bounds check on X (not required. is it faster?)
2980#if 0
2981 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2982 return 0;
2983 }
2984#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002985
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 SkScalar roots[2];
2987 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2988 2 * (pts[1].fY - pts[0].fY),
2989 pts[0].fY - y,
2990 roots);
2991 SkASSERT(n <= 1);
2992 SkScalar xt;
2993 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002994 // zero roots are returned only when y0 == y
2995 // Need [0] if dir == 1
2996 // and [2] if dir == -1
2997 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 } else {
2999 SkScalar t = roots[0];
3000 SkScalar C = pts[0].fX;
3001 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3002 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003003 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 }
caryclark9aacd902015-12-14 08:38:09 -08003005 if (SkScalarNearlyEqual(xt, x)) {
3006 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3007 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003008 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003009 }
3010 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 return xt < x ? dir : 0;
3012}
3013
caryclark9aacd902015-12-14 08:38:09 -08003014static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 SkPoint dst[5];
3016 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003017
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3019 n = SkChopQuadAtYExtrema(pts, dst);
3020 pts = dst;
3021 }
caryclark9aacd902015-12-14 08:38:09 -08003022 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003023 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003024 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 }
3026 return w;
3027}
3028
caryclark9aacd902015-12-14 08:38:09 -08003029static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003030 SkScalar x0 = pts[0].fX;
3031 SkScalar y0 = pts[0].fY;
3032 SkScalar x1 = pts[1].fX;
3033 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003034
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003035 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003036
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 int dir = 1;
3038 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003039 using std::swap;
3040 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003041 dir = -1;
3042 }
caryclark9aacd902015-12-14 08:38:09 -08003043 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003044 return 0;
3045 }
caryclark9cb5d752015-12-18 04:35:24 -08003046 if (checkOnCurve(x, y, pts[0], pts[1])) {
3047 *onCurveCount += 1;
3048 return 0;
3049 }
3050 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003051 return 0;
3052 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003053 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003054
caryclark9aacd902015-12-14 08:38:09 -08003055 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003056 // zero cross means the point is on the line, and since the case where
3057 // y of the query point is at the end point is handled above, we can be
3058 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003059 if (x != x1 || y != pts[1].fY) {
3060 *onCurveCount += 1;
3061 }
caryclark9aacd902015-12-14 08:38:09 -08003062 dir = 0;
3063 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003064 dir = 0;
3065 }
3066 return dir;
3067}
3068
caryclark9aacd902015-12-14 08:38:09 -08003069static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3070 SkTDArray<SkVector>* tangents) {
3071 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3072 && !between(pts[2].fY, y, pts[3].fY)) {
3073 return;
3074 }
3075 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3076 && !between(pts[2].fX, x, pts[3].fX)) {
3077 return;
3078 }
3079 SkPoint dst[10];
3080 int n = SkChopCubicAtYExtrema(pts, dst);
3081 for (int i = 0; i <= n; ++i) {
3082 SkPoint* c = &dst[i * 3];
3083 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003084 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003085 continue;
mbarbella276e6332016-05-31 14:44:01 -07003086 }
caryclark9aacd902015-12-14 08:38:09 -08003087 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3088 if (!SkScalarNearlyEqual(x, xt)) {
3089 continue;
3090 }
3091 SkVector tangent;
3092 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003093 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003094 }
3095}
3096
3097static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3098 SkTDArray<SkVector>* tangents) {
3099 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3100 return;
3101 }
3102 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3103 return;
3104 }
3105 SkScalar roots[2];
3106 SkScalar A = pts[2].fY;
3107 SkScalar B = pts[1].fY * w - y * w + y;
3108 SkScalar C = pts[0].fY;
3109 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3110 B -= C; // B = b*w - w * yCept + yCept - a
3111 C -= y;
3112 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3113 for (int index = 0; index < n; ++index) {
3114 SkScalar t = roots[index];
3115 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3116 if (!SkScalarNearlyEqual(x, xt)) {
3117 continue;
3118 }
3119 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003120 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003121 }
3122}
3123
3124static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3125 SkTDArray<SkVector>* tangents) {
3126 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3127 return;
3128 }
3129 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3130 return;
3131 }
3132 SkScalar roots[2];
3133 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3134 2 * (pts[1].fY - pts[0].fY),
3135 pts[0].fY - y,
3136 roots);
3137 for (int index = 0; index < n; ++index) {
3138 SkScalar t = roots[index];
3139 SkScalar C = pts[0].fX;
3140 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3141 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003142 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003143 if (!SkScalarNearlyEqual(x, xt)) {
3144 continue;
3145 }
Mike Reed5edcd312018-08-08 11:23:41 -04003146 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003147 }
3148}
3149
3150static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3151 SkTDArray<SkVector>* tangents) {
3152 SkScalar y0 = pts[0].fY;
3153 SkScalar y1 = pts[1].fY;
3154 if (!between(y0, y, y1)) {
3155 return;
3156 }
3157 SkScalar x0 = pts[0].fX;
3158 SkScalar x1 = pts[1].fX;
3159 if (!between(x0, x, x1)) {
3160 return;
3161 }
3162 SkScalar dx = x1 - x0;
3163 SkScalar dy = y1 - y0;
3164 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3165 return;
3166 }
3167 SkVector v;
3168 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003169 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003170}
3171
reed@google.com4db592c2013-10-30 17:39:43 +00003172static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3173 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3174}
3175
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176bool SkPath::contains(SkScalar x, SkScalar y) const {
3177 bool isInverse = this->isInverseFillType();
3178 if (this->isEmpty()) {
3179 return isInverse;
3180 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003181
reed@google.com4db592c2013-10-30 17:39:43 +00003182 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003183 return isInverse;
3184 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003185
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 SkPath::Iter iter(*this, true);
3187 bool done = false;
3188 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003189 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003190 do {
3191 SkPoint pts[4];
3192 switch (iter.next(pts, false)) {
3193 case SkPath::kMove_Verb:
3194 case SkPath::kClose_Verb:
3195 break;
3196 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003197 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003198 break;
3199 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003200 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003201 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003202 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003203 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003204 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003205 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003206 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003207 break;
3208 case SkPath::kDone_Verb:
3209 done = true;
3210 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003211 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003212 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003213 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3214 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3215 if (evenOddFill) {
3216 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003217 }
caryclark9aacd902015-12-14 08:38:09 -08003218 if (w) {
3219 return !isInverse;
3220 }
3221 if (onCurveCount <= 1) {
3222 return SkToBool(onCurveCount) ^ isInverse;
3223 }
3224 if ((onCurveCount & 1) || evenOddFill) {
3225 return SkToBool(onCurveCount & 1) ^ isInverse;
3226 }
halcanary9d524f22016-03-29 09:03:52 -07003227 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003228 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3229 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003230 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003231 SkTDArray<SkVector> tangents;
3232 do {
3233 SkPoint pts[4];
3234 int oldCount = tangents.count();
3235 switch (iter.next(pts, false)) {
3236 case SkPath::kMove_Verb:
3237 case SkPath::kClose_Verb:
3238 break;
3239 case SkPath::kLine_Verb:
3240 tangent_line(pts, x, y, &tangents);
3241 break;
3242 case SkPath::kQuad_Verb:
3243 tangent_quad(pts, x, y, &tangents);
3244 break;
3245 case SkPath::kConic_Verb:
3246 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3247 break;
3248 case SkPath::kCubic_Verb:
3249 tangent_cubic(pts, x, y, &tangents);
3250 break;
3251 case SkPath::kDone_Verb:
3252 done = true;
3253 break;
3254 }
3255 if (tangents.count() > oldCount) {
3256 int last = tangents.count() - 1;
3257 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003258 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003259 tangents.remove(last);
3260 } else {
3261 for (int index = 0; index < last; ++index) {
3262 const SkVector& test = tangents[index];
3263 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003264 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3265 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003266 tangents.remove(last);
3267 tangents.removeShuffle(index);
3268 break;
3269 }
3270 }
3271 }
3272 }
3273 } while (!done);
3274 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003275}
fmalitaaa0df4e2015-12-01 09:13:23 -08003276
3277int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3278 SkScalar w, SkPoint pts[], int pow2) {
3279 const SkConic conic(p0, p1, p2, w);
3280 return conic.chopIntoQuadsPOW2(pts, pow2);
3281}
bsalomonedc743a2016-06-01 09:42:31 -07003282
3283bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3284 unsigned* start) {
3285 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3286 return false;
3287 }
3288 SkPath::RawIter iter(path);
3289 SkPoint verbPts[4];
3290 SkPath::Verb v;
3291 SkPoint rectPts[5];
3292 int rectPtCnt = 0;
3293 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3294 switch (v) {
3295 case SkPath::kMove_Verb:
3296 if (0 != rectPtCnt) {
3297 return false;
3298 }
3299 rectPts[0] = verbPts[0];
3300 ++rectPtCnt;
3301 break;
3302 case SkPath::kLine_Verb:
3303 if (5 == rectPtCnt) {
3304 return false;
3305 }
3306 rectPts[rectPtCnt] = verbPts[1];
3307 ++rectPtCnt;
3308 break;
3309 case SkPath::kClose_Verb:
3310 if (4 == rectPtCnt) {
3311 rectPts[4] = rectPts[0];
3312 rectPtCnt = 5;
3313 }
3314 break;
3315 default:
3316 return false;
3317 }
3318 }
3319 if (rectPtCnt < 5) {
3320 return false;
3321 }
3322 if (rectPts[0] != rectPts[4]) {
3323 return false;
3324 }
bsalomon057ae8a2016-07-24 05:37:26 -07003325 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3326 // and pts 1 and 2 the opposite vertical or horizontal edge).
3327 bool vec03IsVertical;
3328 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3329 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3330 // Make sure it has non-zero width and height
3331 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003332 return false;
3333 }
bsalomon057ae8a2016-07-24 05:37:26 -07003334 vec03IsVertical = true;
3335 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3336 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3337 // Make sure it has non-zero width and height
3338 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3339 return false;
3340 }
3341 vec03IsVertical = false;
3342 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003343 return false;
3344 }
bsalomon057ae8a2016-07-24 05:37:26 -07003345 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3346 // set if it is on the bottom edge.
3347 unsigned sortFlags =
3348 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3349 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3350 switch (sortFlags) {
3351 case 0b00:
3352 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3353 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3354 *start = 0;
3355 break;
3356 case 0b01:
3357 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3358 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3359 *start = 1;
3360 break;
3361 case 0b10:
3362 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3363 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3364 *start = 3;
3365 break;
3366 case 0b11:
3367 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3368 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3369 *start = 2;
3370 break;
bsalomonedc743a2016-06-01 09:42:31 -07003371 }
bsalomonedc743a2016-06-01 09:42:31 -07003372 return true;
3373}
bsalomon21af9ca2016-08-25 12:29:23 -07003374
Brian Salomone4949402018-04-26 15:22:04 -04003375bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3376 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3377 // This gets converted to an oval.
3378 return true;
3379 }
3380 if (useCenter) {
3381 // This is a pie wedge. It's convex if the angle is <= 180.
3382 return SkScalarAbs(sweepAngle) <= 180.f;
3383 }
3384 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3385 // to a secant, i.e. convex.
3386 return SkScalarAbs(sweepAngle) <= 360.f;
3387}
3388
bsalomon21af9ca2016-08-25 12:29:23 -07003389void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3390 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3391 SkASSERT(!oval.isEmpty());
3392 SkASSERT(sweepAngle);
3393
3394 path->reset();
3395 path->setIsVolatile(true);
3396 path->setFillType(SkPath::kWinding_FillType);
3397 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3398 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003399 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003400 return;
3401 }
3402 if (useCenter) {
3403 path->moveTo(oval.centerX(), oval.centerY());
3404 }
Brian Salomone4949402018-04-26 15:22:04 -04003405 auto firstDir =
3406 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3407 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003408 // Arc to mods at 360 and drawArc is not supposed to.
3409 bool forceMoveTo = !useCenter;
3410 while (sweepAngle <= -360.f) {
3411 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3412 startAngle -= 180.f;
3413 path->arcTo(oval, startAngle, -180.f, false);
3414 startAngle -= 180.f;
3415 forceMoveTo = false;
3416 sweepAngle += 360.f;
3417 }
3418 while (sweepAngle >= 360.f) {
3419 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3420 startAngle += 180.f;
3421 path->arcTo(oval, startAngle, 180.f, false);
3422 startAngle += 180.f;
3423 forceMoveTo = false;
3424 sweepAngle -= 360.f;
3425 }
3426 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3427 if (useCenter) {
3428 path->close();
3429 }
Brian Salomone4949402018-04-26 15:22:04 -04003430 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3431 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003432}
Mike Reed0d7dac82017-02-02 17:45:56 -08003433
3434///////////////////////////////////////////////////////////////////////////////////////////////////
3435#include "SkNx.h"
3436
3437static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3438 SkScalar ts[2];
3439 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3440 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3441 SkASSERT(n >= 0 && n <= 2);
3442 for (int i = 0; i < n; ++i) {
3443 extremas[i] = SkEvalQuadAt(src, ts[i]);
3444 }
3445 extremas[n] = src[2];
3446 return n + 1;
3447}
3448
3449static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3450 SkConic conic(src[0], src[1], src[2], w);
3451 SkScalar ts[2];
3452 int n = conic.findXExtrema(ts);
3453 n += conic.findYExtrema(&ts[n]);
3454 SkASSERT(n >= 0 && n <= 2);
3455 for (int i = 0; i < n; ++i) {
3456 extremas[i] = conic.evalAt(ts[i]);
3457 }
3458 extremas[n] = src[2];
3459 return n + 1;
3460}
3461
3462static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3463 SkScalar ts[4];
3464 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3465 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3466 SkASSERT(n >= 0 && n <= 4);
3467 for (int i = 0; i < n; ++i) {
3468 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3469 }
3470 extremas[n] = src[3];
3471 return n + 1;
3472}
3473
Mike Reed8d3196b2017-02-03 11:34:13 -05003474SkRect SkPath::computeTightBounds() const {
3475 if (0 == this->countVerbs()) {
3476 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003477 }
3478
Mike Reed8d3196b2017-02-03 11:34:13 -05003479 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3480 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003481 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003482
Mike Reed0d7dac82017-02-02 17:45:56 -08003483 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3484 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003485 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003486
3487 // initial with the first MoveTo, so we don't have to check inside the switch
3488 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003489 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003490 for (;;) {
3491 int count = 0;
3492 switch (iter.next(pts)) {
3493 case SkPath::kMove_Verb:
3494 extremas[0] = pts[0];
3495 count = 1;
3496 break;
3497 case SkPath::kLine_Verb:
3498 extremas[0] = pts[1];
3499 count = 1;
3500 break;
3501 case SkPath::kQuad_Verb:
3502 count = compute_quad_extremas(pts, extremas);
3503 break;
3504 case SkPath::kConic_Verb:
3505 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3506 break;
3507 case SkPath::kCubic_Verb:
3508 count = compute_cubic_extremas(pts, extremas);
3509 break;
3510 case SkPath::kClose_Verb:
3511 break;
3512 case SkPath::kDone_Verb:
3513 goto DONE;
3514 }
3515 for (int i = 0; i < count; ++i) {
3516 Sk2s tmp = from_point(extremas[i]);
3517 min = Sk2s::Min(min, tmp);
3518 max = Sk2s::Max(max, tmp);
3519 }
3520 }
3521DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003522 SkRect bounds;
3523 min.store((SkPoint*)&bounds.fLeft);
3524 max.store((SkPoint*)&bounds.fRight);
3525 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003526}
Cary Clarkdf429f32017-11-08 11:44:31 -05003527
3528bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3529 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3530}
3531
3532bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3533 const SkPoint& p3, bool exact) {
3534 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3535 SkPointPriv::EqualsWithinTolerance(p2, p3);
3536}
3537
3538bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3539 const SkPoint& p3, const SkPoint& p4, bool exact) {
3540 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3541 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3542 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3543 SkPointPriv::EqualsWithinTolerance(p3, p4);
3544}