blob: 95fa37f7a6cb6bdb4fae39fc892c77437d387507 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000019
Mike Reeda99b6ce2017-02-04 11:04:26 -050020static float poly_eval(float A, float B, float C, float t) {
21 return (A * t + B) * t + C;
22}
23
24static float poly_eval(float A, float B, float C, float D, float t) {
25 return ((A * t + B) * t + C) * t + D;
26}
27
reed@android.com8a1c16f2008-12-17 15:59:43 +000028////////////////////////////////////////////////////////////////////////////
29
reed@google.com3563c9e2011-11-14 19:34:57 +000030/**
31 * Path.bounds is defined to be the bounds of all the control points.
32 * If we called bounds.join(r) we would skip r if r was empty, which breaks
33 * our promise. Hence we have a custom joiner that doesn't look at emptiness
34 */
35static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
36 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
37 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
38 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
39 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
40}
41
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000042static bool is_degenerate(const SkPath& path) {
43 SkPath::Iter iter(path, false);
44 SkPoint pts[4];
45 return SkPath::kDone_Verb == iter.next(pts);
46}
47
bsalomon@google.com30c174b2012-11-13 14:36:42 +000048class SkAutoDisableDirectionCheck {
49public:
50 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070051 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052 }
53
54 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070055 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000056 }
57
58private:
reed026beb52015-06-10 14:23:15 -070059 SkPath* fPath;
60 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000061};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000062#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000063
reed@android.com8a1c16f2008-12-17 15:59:43 +000064/* This guy's constructor/destructor bracket a path editing operation. It is
65 used when we know the bounds of the amount we are going to add to the path
66 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000067
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000069 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000070 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000071
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000072 It also notes if the path was originally degenerate, and if so, sets
73 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000074 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 */
76class SkAutoPathBoundsUpdate {
77public:
78 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
79 this->init(path);
80 }
81
82 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
83 SkScalar right, SkScalar bottom) {
84 fRect.set(left, top, right, bottom);
85 this->init(path);
86 }
reed@google.comabf15c12011-01-18 20:35:51 +000087
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000089 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
90 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050091 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000092 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 }
94 }
reed@google.comabf15c12011-01-18 20:35:51 +000095
reed@android.com8a1c16f2008-12-17 15:59:43 +000096private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000097 SkPath* fPath;
98 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000101 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000102
reed@android.com6b82d1a2009-06-03 02:35:01 +0000103 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000104 // Cannot use fRect for our bounds unless we know it is sorted
105 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000107 // Mark the path's bounds as dirty if (1) they are, or (2) the path
108 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000111 if (fHasValidBounds && !fEmpty) {
112 joinNoEmptyChecks(&fRect, fPath->getBounds());
113 }
114 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 }
116};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000117#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119////////////////////////////////////////////////////////////////////////////
120
121/*
122 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000123 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
125
126 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000127 1. If we encounter degenerate segments, remove them
128 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
129 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
130 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131*/
132
133////////////////////////////////////////////////////////////////////////////
134
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135// flag to require a moveTo if we begin with something else, like lineTo etc.
136#define INITIAL_LASTMOVETOINDEX_VALUE ~0
137
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000138SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800139 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700141 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000142}
143
144void SkPath::resetFields() {
145 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000146 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000148 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700149 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000150
151 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700152 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000153}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000156 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 this->copyFields(that);
158 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159}
160
161SkPath::~SkPath() {
162 SkDEBUGCODE(this->validate();)
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165SkPath& SkPath::operator=(const SkPath& that) {
166 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 if (this != &that) {
169 fPathRef.reset(SkRef(that.fPathRef.get()));
170 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 }
172 SkDEBUGCODE(this->validate();)
173 return *this;
174}
175
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176void SkPath::copyFields(const SkPath& that) {
177 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000178 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700180 fIsVolatile = that.fIsVolatile;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400181
182 // Non-atomic assignment of atomic values.
183 fConvexity .store(that.fConvexity .load());
184 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185}
186
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000187bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000188 // note: don't need to look at isConvex or bounds, since just comparing the
189 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000190 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000191 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000192}
193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700196 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000197 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700199 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400200
201 // Non-atomic swaps of atomic values.
202 Convexity c = fConvexity.load();
203 fConvexity.store(that.fConvexity.load());
204 that.fConvexity.store(c);
205
206 uint8_t fd = fFirstDirection.load();
207 fFirstDirection.store(that.fFirstDirection.load());
208 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 }
210}
211
caryclark8e7b19d2016-02-18 04:11:48 -0800212bool SkPath::isInterpolatable(const SkPath& compare) const {
213 int count = fPathRef->countVerbs();
214 if (count != compare.fPathRef->countVerbs()) {
215 return false;
216 }
217 if (!count) {
218 return true;
219 }
220 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
221 count)) {
222 return false;
223 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800224 return !fPathRef->countWeights() ||
225 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800226 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
227}
228
229bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
230 int verbCount = fPathRef->countVerbs();
231 if (verbCount != ending.fPathRef->countVerbs()) {
232 return false;
233 }
caryclarka1105382016-02-18 06:13:25 -0800234 if (!verbCount) {
235 return true;
236 }
caryclark8e7b19d2016-02-18 04:11:48 -0800237 out->reset();
238 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700239 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800240 return true;
241}
242
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000243static inline bool check_edge_against_rect(const SkPoint& p0,
244 const SkPoint& p1,
245 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700246 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 const SkPoint* edgeBegin;
248 SkVector v;
reed026beb52015-06-10 14:23:15 -0700249 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 v = p1 - p0;
251 edgeBegin = &p0;
252 } else {
253 v = p0 - p1;
254 edgeBegin = &p1;
255 }
256 if (v.fX || v.fY) {
257 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500258 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
259 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
260 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
261 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
263 return false;
264 }
265 }
266 return true;
267}
268
269bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
270 // This only handles non-degenerate convex paths currently.
271 if (kConvex_Convexity != this->getConvexity()) {
272 return false;
273 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000274
reed026beb52015-06-10 14:23:15 -0700275 SkPathPriv::FirstDirection direction;
276 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 return false;
278 }
279
280 SkPoint firstPt;
281 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500282 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000283 SkPath::Verb verb;
284 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400285 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288
Lee Salzmana19f0242017-01-12 13:06:21 -0500289 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 int nextPt = -1;
291 switch (verb) {
292 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 SkDEBUGCODE(++moveCnt);
295 firstPt = prevPt = pts[0];
296 break;
297 case kLine_Verb:
298 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000299 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400300 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 break;
302 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000303 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000304 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400305 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000306 nextPt = 2;
307 break;
308 case kCubic_Verb:
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 nextPt = 3;
312 break;
313 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000314 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315 break;
316 default:
317 SkDEBUGFAIL("unknown verb");
318 }
319 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800320 if (SkPath::kConic_Verb == verb) {
321 SkConic orig;
322 orig.set(pts, iter.conicWeight());
323 SkPoint quadPts[5];
324 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800325 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800326
327 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
328 return false;
329 }
330 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
331 return false;
332 }
333 } else {
334 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
335 return false;
336 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000337 }
338 prevPt = pts[nextPt];
339 }
340 }
341
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400342 if (segmentCount) {
343 return check_edge_against_rect(prevPt, firstPt, rect, direction);
344 }
345 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000346}
347
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000348uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000349 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800350#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400351 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
352 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000353#endif
354 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000355}
djsollen@google.come63793a2012-03-21 15:39:03 +0000356
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357void SkPath::reset() {
358 SkDEBUGCODE(this->validate();)
359
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000361 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362}
363
364void SkPath::rewind() {
365 SkDEBUGCODE(this->validate();)
366
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000367 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000368 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369}
370
fsb1475b02016-01-20 09:51:07 -0800371bool SkPath::isLastContourClosed() const {
372 int verbCount = fPathRef->countVerbs();
373 if (0 == verbCount) {
374 return false;
375 }
376 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
377}
378
reed@google.com7e6c4d12012-05-10 14:05:43 +0000379bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000380 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000382 if (2 == verbCount) {
383 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
384 if (kLine_Verb == fPathRef->atVerb(1)) {
385 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000386 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000387 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000388 line[0] = pts[0];
389 line[1] = pts[1];
390 }
391 return true;
392 }
393 }
394 return false;
395}
396
caryclark@google.comf1316942011-07-26 19:54:45 +0000397/*
398 Determines if path is a rect by keeping track of changes in direction
399 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000400
caryclark@google.comf1316942011-07-26 19:54:45 +0000401 The direction is computed such that:
402 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000403 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000405 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000406
caryclark@google.comf1316942011-07-26 19:54:45 +0000407A rectangle cycles up/right/down/left or up/left/down/right.
408
409The test fails if:
410 The path is closed, and followed by a line.
411 A second move creates a new endpoint.
412 A diagonal line is parsed.
413 There's more than four changes of direction.
414 There's a discontinuity on the line (e.g., a move in the middle)
415 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000416 The path contains a quadratic or cubic.
417 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000418 *The rectangle doesn't complete a cycle.
419 *The final point isn't equal to the first point.
420
421 *These last two conditions we relax if we have a 3-edge path that would
422 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000423
caryclark@google.comf1316942011-07-26 19:54:45 +0000424It's OK if the path has:
425 Several colinear line segments composing a rectangle side.
426 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428The direction takes advantage of the corners found since opposite sides
429must travel in opposite directions.
430
431FIXME: Allow colinear quads and cubics to be treated like lines.
432FIXME: If the API passes fill-only, return true if the filled stroke
433 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000434
435 first,last,next direction state-machine:
436 0x1 is set if the segment is horizontal
437 0x2 is set if the segment is moving to the right or down
438 thus:
439 two directions are opposites iff (dirA ^ dirB) == 0x2
440 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000441
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000443static int rect_make_dir(SkScalar dx, SkScalar dy) {
444 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
445}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000446bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
447 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 int corners = 0;
449 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000450 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700451 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 first.set(0, 0);
453 last.set(0, 0);
454 int firstDirection = 0;
455 int lastDirection = 0;
456 int nextDirection = 0;
457 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700459 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000460 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000461 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700462 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
463 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000465 savePts = pts;
466 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000467 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700468 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 case kLine_Verb: {
470 SkScalar left = last.fX;
471 SkScalar top = last.fY;
472 SkScalar right = pts->fX;
473 SkScalar bottom = pts->fY;
474 ++pts;
475 if (left != right && top != bottom) {
476 return false; // diagonal
477 }
478 if (left == right && top == bottom) {
479 break; // single point on side OK
480 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000481 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 if (0 == corners) {
483 firstDirection = nextDirection;
484 first = last;
485 last = pts[-1];
486 corners = 1;
487 closedOrMoved = false;
488 break;
489 }
490 if (closedOrMoved) {
491 return false; // closed followed by a line
492 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000493 if (autoClose && nextDirection == firstDirection) {
494 break; // colinear with first
495 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 closedOrMoved = autoClose;
497 if (lastDirection != nextDirection) {
498 if (++corners > 4) {
499 return false; // too many direction changes
500 }
501 }
502 last = pts[-1];
503 if (lastDirection == nextDirection) {
504 break; // colinear segment
505 }
506 // Possible values for corners are 2, 3, and 4.
507 // When corners == 3, nextDirection opposes firstDirection.
508 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000509 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
511 if ((directionCycle ^ turn) != nextDirection) {
512 return false; // direction didn't follow cycle
513 }
514 break;
515 }
516 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000517 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 case kCubic_Verb:
519 return false; // quadratic, cubic not allowed
520 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700521 if (allowPartial && !autoClose && firstDirection) {
522 insertClose = true;
523 *currVerb -= 1; // try move again afterwards
524 goto addMissingClose;
525 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000526 last = *pts++;
527 closedOrMoved = true;
528 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000529 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000530 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000531 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000532 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000534 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700535addMissingClose:
536 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 }
538 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000539 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000540 if (!result) {
541 // check if we are just an incomplete rectangle, in which case we can
542 // return true, but not claim to be closed.
543 // e.g.
544 // 3 sided rectangle
545 // 4 sided but the last edge is not long enough to reach the start
546 //
547 SkScalar closeX = first.x() - last.x();
548 SkScalar closeY = first.y() - last.y();
549 if (closeX && closeY) {
550 return false; // we're diagonal, abort (can we ever reach this?)
551 }
552 int closeDirection = rect_make_dir(closeX, closeY);
553 // make sure the close-segment doesn't double-back on itself
554 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
555 result = true;
556 autoClose = false; // we are not closed
557 }
558 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000559 if (savePts) {
560 *ptsPtr = savePts;
561 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000562 if (result && isClosed) {
563 *isClosed = autoClose;
564 }
565 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000566 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 return result;
569}
570
robertphillips4f662e62014-12-29 14:06:51 -0800571bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000572 SkDEBUGCODE(this->validate();)
573 int currVerb = 0;
574 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800575 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800576 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800577 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000578 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800579 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800580 int32_t num = SkToS32(pts - first);
581 if (num) {
582 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800583 } else {
584 // 'pts' isn't updated for open rects
585 *rect = this->getBounds();
586 }
587 }
588 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000589}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000590
caryclark95bc5f32015-04-08 08:34:15 -0700591bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592 SkDEBUGCODE(this->validate();)
593 int currVerb = 0;
594 const SkPoint* pts = fPathRef->points();
595 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000596 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700597 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 return false;
599 }
600 const SkPoint* last = pts;
601 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700602 bool isClosed;
603 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000604 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700605 if (!isClosed) {
606 pts = fPathRef->points() + fPathRef->countPoints();
607 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000608 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 if (testRects[0].contains(testRects[1])) {
610 if (rects) {
611 rects[0] = testRects[0];
612 rects[1] = testRects[1];
613 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (dirs) {
615 dirs[0] = testDirs[0];
616 dirs[1] = testDirs[1];
617 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000618 return true;
619 }
620 if (testRects[1].contains(testRects[0])) {
621 if (rects) {
622 rects[0] = testRects[1];
623 rects[1] = testRects[0];
624 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000625 if (dirs) {
626 dirs[0] = testDirs[1];
627 dirs[1] = testDirs[0];
628 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000629 return true;
630 }
631 }
632 return false;
633}
634
Mike Reed0c3137c2018-02-20 13:57:05 -0500635bool SkPath::isOval(SkRect* bounds) const {
636 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
637}
638
639bool SkPath::isRRect(SkRRect* rrect) const {
640 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
641}
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643int SkPath::countPoints() const {
644 return fPathRef->countPoints();
645}
646
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkDEBUGCODE(this->validate();)
649
650 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000652 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800653 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655}
656
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000657SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
659 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000660 }
661 return SkPoint::Make(0, 0);
662}
663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664int SkPath::countVerbs() const {
665 return fPathRef->countVerbs();
666}
667
668static inline void copy_verbs_reverse(uint8_t* inorderDst,
669 const uint8_t* reversedSrc,
670 int count) {
671 for (int i = 0; i < count; ++i) {
672 inorderDst[i] = reversedSrc[~i];
673 }
674}
675
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676int SkPath::getVerbs(uint8_t dst[], int max) const {
677 SkDEBUGCODE(this->validate();)
678
679 SkASSERT(max >= 0);
680 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = SkMin32(max, fPathRef->countVerbs());
682 copy_verbs_reverse(dst, fPathRef->verbs(), count);
683 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000684}
685
reed@google.com294dd7b2011-10-11 11:58:32 +0000686bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkDEBUGCODE(this->validate();)
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000690 if (count > 0) {
691 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 if (lastPt) {
697 lastPt->set(0, 0);
698 }
699 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
caryclarkaec25102015-04-29 08:28:30 -0700702void SkPath::setPt(int index, SkScalar x, SkScalar y) {
703 SkDEBUGCODE(this->validate();)
704
705 int count = fPathRef->countPoints();
706 if (count <= index) {
707 return;
708 } else {
709 SkPathRef::Editor ed(&fPathRef);
710 ed.atPoint(index)->set(x, y);
711 }
712}
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::setLastPt(SkScalar x, SkScalar y) {
715 SkDEBUGCODE(this->validate();)
716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 if (count == 0) {
719 this->moveTo(x, y);
720 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 SkPathRef::Editor ed(&fPathRef);
722 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 }
724}
725
reed@google.com04863fa2011-05-15 04:08:24 +0000726void SkPath::setConvexity(Convexity c) {
727 if (fConvexity != c) {
728 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000729 }
730}
731
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732//////////////////////////////////////////////////////////////////////////////
733// Construction methods
734
reed026beb52015-06-10 14:23:15 -0700735#define DIRTY_AFTER_EDIT \
736 do { \
737 fConvexity = kUnknown_Convexity; \
738 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000739 } while (0)
740
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741void SkPath::incReserve(U16CPU inc) {
742 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000743 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744 SkDEBUGCODE(this->validate();)
745}
746
747void SkPath::moveTo(SkScalar x, SkScalar y) {
748 SkDEBUGCODE(this->validate();)
749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752 // remember our index
753 fLastMoveToIndex = fPathRef->countPoints();
754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700756
757 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758}
759
760void SkPath::rMoveTo(SkScalar x, SkScalar y) {
761 SkPoint pt;
762 this->getLastPt(&pt);
763 this->moveTo(pt.fX + x, pt.fY + y);
764}
765
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000766void SkPath::injectMoveToIfNeeded() {
767 if (fLastMoveToIndex < 0) {
768 SkScalar x, y;
769 if (fPathRef->countVerbs() == 0) {
770 x = y = 0;
771 } else {
772 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
773 x = pt.fX;
774 y = pt.fY;
775 }
776 this->moveTo(x, y);
777 }
778}
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780void SkPath::lineTo(SkScalar x, SkScalar y) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787
reed@google.comb54455e2011-05-16 14:16:04 +0000788 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789}
790
791void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000792 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 SkPoint pt;
794 this->getLastPt(&pt);
795 this->lineTo(pt.fX + x, pt.fY + y);
796}
797
798void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
799 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000800
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000801 this->injectMoveToIfNeeded();
802
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000803 SkPathRef::Editor ed(&fPathRef);
804 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 pts[0].set(x1, y1);
806 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000807
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809}
810
811void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000812 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 SkPoint pt;
814 this->getLastPt(&pt);
815 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
816}
817
reed@google.com277c3f82013-05-31 15:17:50 +0000818void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
819 SkScalar w) {
820 // check for <= 0 or NaN with this test
821 if (!(w > 0)) {
822 this->lineTo(x2, y2);
823 } else if (!SkScalarIsFinite(w)) {
824 this->lineTo(x1, y1);
825 this->lineTo(x2, y2);
826 } else if (SK_Scalar1 == w) {
827 this->quadTo(x1, y1, x2, y2);
828 } else {
829 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000830
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000831 this->injectMoveToIfNeeded();
832
reed@google.com277c3f82013-05-31 15:17:50 +0000833 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000834 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000835 pts[0].set(x1, y1);
836 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000837
reed@google.com277c3f82013-05-31 15:17:50 +0000838 DIRTY_AFTER_EDIT;
839 }
840}
841
842void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
843 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000844 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000845 SkPoint pt;
846 this->getLastPt(&pt);
847 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
848}
849
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
851 SkScalar x3, SkScalar y3) {
852 SkDEBUGCODE(this->validate();)
853
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000854 this->injectMoveToIfNeeded();
855
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 SkPathRef::Editor ed(&fPathRef);
857 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 pts[0].set(x1, y1);
859 pts[1].set(x2, y2);
860 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861
reed@google.comb54455e2011-05-16 14:16:04 +0000862 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863}
864
865void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
866 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000867 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkPoint pt;
869 this->getLastPt(&pt);
870 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
871 pt.fX + x3, pt.fY + y3);
872}
873
874void SkPath::close() {
875 SkDEBUGCODE(this->validate();)
876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 case kLine_Verb:
881 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000882 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 case kMove_Verb: {
885 SkPathRef::Editor ed(&fPathRef);
886 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 }
reed@google.com277c3f82013-05-31 15:17:50 +0000889 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000890 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000891 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000892 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000893 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000894 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000895 }
896 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000897
898 // signal that we need a moveTo to follow us (unless we're done)
899#if 0
900 if (fLastMoveToIndex >= 0) {
901 fLastMoveToIndex = ~fLastMoveToIndex;
902 }
903#else
904 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
905#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906}
907
908///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000909
fmalitac08d53e2015-11-17 09:53:29 -0800910namespace {
911
912template <unsigned N>
913class PointIterator {
914public:
915 PointIterator(SkPath::Direction dir, unsigned startIndex)
916 : fCurrent(startIndex % N)
917 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
918
919 const SkPoint& current() const {
920 SkASSERT(fCurrent < N);
921 return fPts[fCurrent];
922 }
923
924 const SkPoint& next() {
925 fCurrent = (fCurrent + fAdvance) % N;
926 return this->current();
927 }
928
929protected:
930 SkPoint fPts[N];
931
932private:
933 unsigned fCurrent;
934 unsigned fAdvance;
935};
936
937class RectPointIterator : public PointIterator<4> {
938public:
939 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
940 : PointIterator(dir, startIndex) {
941
942 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
943 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
944 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
945 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
946 }
947};
948
949class OvalPointIterator : public PointIterator<4> {
950public:
951 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
952 : PointIterator(dir, startIndex) {
953
954 const SkScalar cx = oval.centerX();
955 const SkScalar cy = oval.centerY();
956
957 fPts[0] = SkPoint::Make(cx, oval.fTop);
958 fPts[1] = SkPoint::Make(oval.fRight, cy);
959 fPts[2] = SkPoint::Make(cx, oval.fBottom);
960 fPts[3] = SkPoint::Make(oval.fLeft, cy);
961 }
962};
963
964class RRectPointIterator : public PointIterator<8> {
965public:
966 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
967 : PointIterator(dir, startIndex) {
968
969 const SkRect& bounds = rrect.getBounds();
970 const SkScalar L = bounds.fLeft;
971 const SkScalar T = bounds.fTop;
972 const SkScalar R = bounds.fRight;
973 const SkScalar B = bounds.fBottom;
974
975 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
976 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
977 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
978 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
979 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
980 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
981 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
982 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
983 }
984};
985
986} // anonymous namespace
987
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000988static void assert_known_direction(int dir) {
989 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
990}
991
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800993 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994}
995
996void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
997 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800998 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
999}
1000
1001void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001002 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001003 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001004 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001005 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001006 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001007
fmalitac08d53e2015-11-17 09:53:29 -08001008 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 const int kVerbs = 5; // moveTo + 3x lineTo + close
1011 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012
fmalitac08d53e2015-11-17 09:53:29 -08001013 RectPointIterator iter(rect, dir, startIndex);
1014
1015 this->moveTo(iter.current());
1016 this->lineTo(iter.next());
1017 this->lineTo(iter.next());
1018 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001020
1021 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022}
1023
reed@google.com744faba2012-05-29 19:54:52 +00001024void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1025 SkDEBUGCODE(this->validate();)
1026 if (count <= 0) {
1027 return;
1028 }
1029
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001030 fLastMoveToIndex = fPathRef->countPoints();
1031
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 // +close makes room for the extra kClose_Verb
1033 SkPathRef::Editor ed(&fPathRef, count+close, count);
1034
1035 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001036 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1038 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001040
reed@google.com744faba2012-05-29 19:54:52 +00001041 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001043 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001044 }
1045
reed@google.com744faba2012-05-29 19:54:52 +00001046 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001047 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001048}
1049
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050#include "SkGeometry.h"
1051
reedf90ea012015-01-29 12:03:58 -08001052static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1053 SkPoint* pt) {
1054 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 // Chrome uses this path to move into and out of ovals. If not
1056 // treated as a special case the moves can distort the oval's
1057 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001058 pt->set(oval.fRight, oval.centerY());
1059 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 } else if (0 == oval.width() && 0 == oval.height()) {
1061 // Chrome will sometimes create 0 radius round rects. Having degenerate
1062 // quad segments in the path prevents the path from being recognized as
1063 // a rect.
1064 // TODO: optimizing the case where only one of width or height is zero
1065 // should also be considered. This case, however, doesn't seem to be
1066 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001067 pt->set(oval.fRight, oval.fTop);
1068 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069 }
reedf90ea012015-01-29 12:03:58 -08001070 return false;
1071}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072
reedd5d27d92015-02-09 13:54:43 -08001073// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1074//
1075static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1076 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1077 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1078 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079
1080 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001081 loss in radians-conversion and/or sin/cos, we may end up with coincident
1082 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1083 of drawing a nearly complete circle (good).
1084 e.g. canvas.drawArc(0, 359.99, ...)
1085 -vs- canvas.drawArc(0, 359.9, ...)
1086 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001087 */
reedd5d27d92015-02-09 13:54:43 -08001088 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 SkScalar sw = SkScalarAbs(sweepAngle);
1090 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1091 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1092 // make a guess at a tiny angle (in radians) to tweak by
1093 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1094 // not sure how much will be enough, so we use a loop
1095 do {
1096 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001097 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1098 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099 }
1100 }
reedd5d27d92015-02-09 13:54:43 -08001101 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1102}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103
reed9e779d42015-02-17 11:43:14 -08001104/**
1105 * If this returns 0, then the caller should just line-to the singlePt, else it should
1106 * ignore singlePt and append the specified number of conics.
1107 */
reedd5d27d92015-02-09 13:54:43 -08001108static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001109 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1110 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001111 SkMatrix matrix;
1112
1113 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1114 matrix.postTranslate(oval.centerX(), oval.centerY());
1115
reed9e779d42015-02-17 11:43:14 -08001116 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1117 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001118 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001119 }
1120 return count;
reedd5d27d92015-02-09 13:54:43 -08001121}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001122
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125 SkRRect rrect;
1126 rrect.setRectRadii(rect, (const SkVector*) radii);
1127 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128}
1129
reed@google.com4ed0fb72012-12-12 20:48:18 +00001130void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001131 // legacy start indices: 6 (CW) and 7(CCW)
1132 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1133}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001134
fmalitac08d53e2015-11-17 09:53:29 -08001135void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1136 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001137
caryclarkda707bf2015-11-19 14:47:43 -08001138 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001139 const SkRect& bounds = rrect.getBounds();
1140
Brian Salomon0a241ce2017-12-15 11:31:05 -05001141 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001142 // degenerate(rect) => radii points are collapsing
1143 this->addRect(bounds, dir, (startIndex + 1) / 2);
1144 } else if (rrect.isOval()) {
1145 // degenerate(oval) => line points are collapsing
1146 this->addOval(bounds, dir, startIndex / 2);
1147 } else {
1148 fFirstDirection = this->hasOnlyMoveTos() ?
1149 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1150
1151 SkAutoPathBoundsUpdate apbu(this, bounds);
1152 SkAutoDisableDirectionCheck addc(this);
1153
1154 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1155 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1156 const SkScalar weight = SK_ScalarRoot2Over2;
1157
1158 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1159 const int kVerbs = startsWithConic
1160 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1161 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1162 this->incReserve(kVerbs);
1163
1164 RRectPointIterator rrectIter(rrect, dir, startIndex);
1165 // Corner iterator indices follow the collapsed radii model,
1166 // adjusted such that the start pt is "behind" the radii start pt.
1167 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1168 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1169
1170 this->moveTo(rrectIter.current());
1171 if (startsWithConic) {
1172 for (unsigned i = 0; i < 3; ++i) {
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 this->lineTo(rrectIter.next());
1175 }
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 // final lineTo handled by close().
1178 } else {
1179 for (unsigned i = 0; i < 4; ++i) {
1180 this->lineTo(rrectIter.next());
1181 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1182 }
1183 }
1184 this->close();
1185
caryclarkda707bf2015-11-19 14:47:43 -08001186 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001187 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001188
fmalitac08d53e2015-11-17 09:53:29 -08001189 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1190 }
1191
1192 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001193}
1194
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001195bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001196 int count = fPathRef->countVerbs();
1197 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1198 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001199 if (*verbs == kLine_Verb ||
1200 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001201 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001202 *verbs == kCubic_Verb) {
1203 return false;
1204 }
1205 ++verbs;
1206 }
1207 return true;
1208}
1209
Brian Osmana2318572017-07-10 15:09:26 -04001210bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1211 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001212 if (count < 2) {
1213 return true;
1214 }
Brian Osmana2318572017-07-10 15:09:26 -04001215 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001216 const SkPoint& first = *pts;
1217 for (int index = 1; index < count; ++index) {
1218 if (first != pts[index]) {
1219 return false;
1220 }
1221 }
1222 return true;
1223}
1224
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001225void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1226 Direction dir) {
1227 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001228
humper@google.com75e3ca12013-04-08 21:44:11 +00001229 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001230 return;
1231 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001232
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001233 SkRRect rrect;
1234 rrect.setRectXY(rect, rx, ry);
1235 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001236}
1237
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001239 // legacy start index: 1
1240 this->addOval(oval, dir, 1);
1241}
1242
1243void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001244 assert_known_direction(dir);
1245
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001246 /* If addOval() is called after previous moveTo(),
1247 this path is still marked as an oval. This is used to
1248 fit into WebKit's calling sequences.
1249 We can't simply check isEmpty() in this case, as additional
1250 moveTo() would mark the path non empty.
1251 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001252 bool isOval = hasOnlyMoveTos();
1253 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001254 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001255 } else {
reed026beb52015-06-10 14:23:15 -07001256 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001258
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 SkAutoPathBoundsUpdate apbu(this, oval);
1261
fmalitac08d53e2015-11-17 09:53:29 -08001262 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1263 const int kVerbs = 6; // moveTo + 4x conicTo + close
1264 this->incReserve(kVerbs);
1265
1266 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1267 // The corner iterator pts are tracking "behind" the oval/radii pts.
1268 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001269 const SkScalar weight = SK_ScalarRoot2Over2;
1270
fmalitac08d53e2015-11-17 09:53:29 -08001271 this->moveTo(ovalIter.current());
1272 for (unsigned i = 0; i < 4; ++i) {
1273 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001274 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276
fmalitac08d53e2015-11-17 09:53:29 -08001277 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1278
robertphillips@google.com466310d2013-12-03 16:43:54 +00001279 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001280
bsalomon78d58d12016-05-27 09:17:04 -07001281 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282}
1283
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1285 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001286 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 }
1288}
1289
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1291 bool forceMoveTo) {
1292 if (oval.width() < 0 || oval.height() < 0) {
1293 return;
1294 }
1295
reedf90ea012015-01-29 12:03:58 -08001296 if (fPathRef->countVerbs() == 0) {
1297 forceMoveTo = true;
1298 }
1299
1300 SkPoint lonePt;
1301 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1302 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1303 return;
1304 }
1305
reedd5d27d92015-02-09 13:54:43 -08001306 SkVector startV, stopV;
1307 SkRotationDirection dir;
1308 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1309
reed9e779d42015-02-17 11:43:14 -08001310 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001311
1312 // At this point, we know that the arc is not a lone point, but startV == stopV
1313 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1314 // cannot handle it.
1315 if (startV == stopV) {
1316 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1317 SkScalar radiusX = oval.width() / 2;
1318 SkScalar radiusY = oval.height() / 2;
1319 // We cannot use SkScalarSinCos function in the next line because
1320 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1321 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001322 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001323 // make sin(endAngle) to be 0 which will then draw a dot.
1324 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1325 oval.centerY() + radiusY * sk_float_sin(endAngle));
1326 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1327 return;
1328 }
1329
reedd5d27d92015-02-09 13:54:43 -08001330 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001331 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001332 if (count) {
1333 this->incReserve(count * 2 + 1);
1334 const SkPoint& pt = conics[0].fPts[0];
1335 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1336 for (int i = 0; i < count; ++i) {
1337 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1338 }
reed9e779d42015-02-17 11:43:14 -08001339 } else {
1340 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001341 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342}
1343
caryclark55d49052016-01-23 05:07:04 -08001344// This converts the SVG arc to conics.
1345// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1346// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1347// See also SVG implementation notes:
1348// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1349// Note that arcSweep bool value is flipped from the original implementation.
1350void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1351 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001352 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001353 SkPoint srcPts[2];
1354 this->getLastPt(&srcPts[0]);
1355 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1356 // joining the endpoints.
1357 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1358 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001359 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001360 return;
1361 }
1362 // If the current point and target point for the arc are identical, it should be treated as a
1363 // zero length path. This ensures continuity in animations.
1364 srcPts[1].set(x, y);
1365 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001366 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001367 return;
1368 }
1369 rx = SkScalarAbs(rx);
1370 ry = SkScalarAbs(ry);
1371 SkVector midPointDistance = srcPts[0] - srcPts[1];
1372 midPointDistance *= 0.5f;
1373
1374 SkMatrix pointTransform;
1375 pointTransform.setRotate(-angle);
1376
1377 SkPoint transformedMidPoint;
1378 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1379 SkScalar squareRx = rx * rx;
1380 SkScalar squareRy = ry * ry;
1381 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1382 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1383
1384 // Check if the radii are big enough to draw the arc, scale radii if not.
1385 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1386 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1387 if (radiiScale > 1) {
1388 radiiScale = SkScalarSqrt(radiiScale);
1389 rx *= radiiScale;
1390 ry *= radiiScale;
1391 }
1392
1393 pointTransform.setScale(1 / rx, 1 / ry);
1394 pointTransform.preRotate(-angle);
1395
1396 SkPoint unitPts[2];
1397 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1398 SkVector delta = unitPts[1] - unitPts[0];
1399
1400 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1401 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1402
1403 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1404 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1405 scaleFactor = -scaleFactor;
1406 }
1407 delta.scale(scaleFactor);
1408 SkPoint centerPoint = unitPts[0] + unitPts[1];
1409 centerPoint *= 0.5f;
1410 centerPoint.offset(-delta.fY, delta.fX);
1411 unitPts[0] -= centerPoint;
1412 unitPts[1] -= centerPoint;
1413 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1414 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1415 SkScalar thetaArc = theta2 - theta1;
1416 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1417 thetaArc += SK_ScalarPI * 2;
1418 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1419 thetaArc -= SK_ScalarPI * 2;
1420 }
1421 pointTransform.setRotate(angle);
1422 pointTransform.preScale(rx, ry);
1423
Cary Clark1acd3c72017-12-08 11:37:01 -05001424#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001425 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001426#else
1427 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1428 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1429#endif
caryclark55d49052016-01-23 05:07:04 -08001430 SkScalar thetaWidth = thetaArc / segments;
1431 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1432 if (!SkScalarIsFinite(t)) {
1433 return;
1434 }
1435 SkScalar startTheta = theta1;
1436 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001437#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1438 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1439 return scalar == SkScalarFloorToScalar(scalar);
1440 };
1441 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1442 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1443 scalar_is_integer(x) && scalar_is_integer(y);
1444#endif
caryclark55d49052016-01-23 05:07:04 -08001445 for (int i = 0; i < segments; ++i) {
1446 SkScalar endTheta = startTheta + thetaWidth;
1447 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1448
1449 unitPts[1].set(cosEndTheta, sinEndTheta);
1450 unitPts[1] += centerPoint;
1451 unitPts[0] = unitPts[1];
1452 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1453 SkPoint mapped[2];
1454 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001455 /*
1456 Computing the arc width introduces rounding errors that cause arcs to start
1457 outside their marks. A round rect may lose convexity as a result. If the input
1458 values are on integers, place the conic on integers as well.
1459 */
1460#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1461 if (expectIntegers) {
1462 SkScalar* mappedScalars = &mapped[0].fX;
1463 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1464 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1465 }
1466 }
1467#endif
caryclark55d49052016-01-23 05:07:04 -08001468 this->conicTo(mapped[0], mapped[1], w);
1469 startTheta = endTheta;
1470 }
1471}
1472
1473void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1474 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1475 SkPoint currentPoint;
1476 this->getLastPt(&currentPoint);
1477 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1478}
1479
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001480void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 if (oval.isEmpty() || 0 == sweepAngle) {
1482 return;
1483 }
1484
1485 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1486
1487 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001488 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1489 // See SkPath::addOval() docs.
1490 SkScalar startOver90 = startAngle / 90.f;
1491 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1492 SkScalar error = startOver90 - startOver90I;
1493 if (SkScalarNearlyEqual(error, 0)) {
1494 // Index 1 is at startAngle == 0.
1495 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1496 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1497 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1498 (unsigned) startIndex);
1499 return;
1500 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 }
bsalomon1978ce22016-05-31 10:42:16 -07001502 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503}
1504
1505/*
1506 Need to handle the case when the angle is sharp, and our computed end-points
1507 for the arc go behind pt1 and/or p2...
1508*/
reedc7789042015-01-29 12:59:11 -08001509void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001510 if (radius == 0) {
1511 this->lineTo(x1, y1);
1512 return;
1513 }
1514
1515 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001516
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 // need to know our prev pt so we can construct tangent vectors
1518 {
1519 SkPoint start;
1520 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001521 // Handle degenerate cases by adding a line to the first point and
1522 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 before.setNormalize(x1 - start.fX, y1 - start.fY);
1524 after.setNormalize(x2 - x1, y2 - y1);
1525 }
reed@google.comabf15c12011-01-18 20:35:51 +00001526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 SkScalar cosh = SkPoint::DotProduct(before, after);
1528 SkScalar sinh = SkPoint::CrossProduct(before, after);
1529
1530 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001531 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 return;
1533 }
reed@google.comabf15c12011-01-18 20:35:51 +00001534
Mike Reeda99b6ce2017-02-04 11:04:26 -05001535 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536
Mike Reeda99b6ce2017-02-04 11:04:26 -05001537 SkScalar xx = x1 - dist * before.fX;
1538 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001539 after.setLength(dist);
1540 this->lineTo(xx, yy);
1541 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1542 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543}
1544
1545///////////////////////////////////////////////////////////////////////////////
1546
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001547void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 SkMatrix matrix;
1549
1550 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001551 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552}
1553
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001554void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001555 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001557 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 SkPoint pts[4];
1559 Verb verb;
1560
Cary Clark9480d822017-10-19 18:01:13 -04001561 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001562 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 while ((verb = iter.next(pts)) != kDone_Verb) {
1564 switch (verb) {
1565 case kMove_Verb:
1566 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001567 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1568 injectMoveToIfNeeded(); // In case last contour is closed
1569 this->lineTo(pts[0]);
1570 } else {
1571 this->moveTo(pts[0]);
1572 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 break;
1574 case kLine_Verb:
1575 proc(matrix, &pts[1], &pts[1], 1);
1576 this->lineTo(pts[1]);
1577 break;
1578 case kQuad_Verb:
1579 proc(matrix, &pts[1], &pts[1], 2);
1580 this->quadTo(pts[1], pts[2]);
1581 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001582 case kConic_Verb:
1583 proc(matrix, &pts[1], &pts[1], 2);
1584 this->conicTo(pts[1], pts[2], iter.conicWeight());
1585 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 case kCubic_Verb:
1587 proc(matrix, &pts[1], &pts[1], 3);
1588 this->cubicTo(pts[1], pts[2], pts[3]);
1589 break;
1590 case kClose_Verb:
1591 this->close();
1592 break;
1593 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001594 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001596 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 }
1598}
1599
1600///////////////////////////////////////////////////////////////////////////////
1601
reed@google.com277c3f82013-05-31 15:17:50 +00001602static int pts_in_verb(unsigned verb) {
1603 static const uint8_t gPtsInVerb[] = {
1604 1, // kMove
1605 1, // kLine
1606 2, // kQuad
1607 2, // kConic
1608 3, // kCubic
1609 0, // kClose
1610 0 // kDone
1611 };
1612
1613 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1614 return gPtsInVerb[verb];
1615}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001616
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617// ignore the last point of the 1st contour
1618void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001619 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1620 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 return;
1622 }
caryclark51c56782016-11-07 05:09:28 -08001623 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1624 SkASSERT(verbsEnd[0] == kMove_Verb);
1625 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1626 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627
caryclark51c56782016-11-07 05:09:28 -08001628 while (verbs < verbsEnd) {
1629 uint8_t v = *verbs++;
1630 pts -= pts_in_verb(v);
1631 switch (v) {
1632 case kMove_Verb:
1633 // if the path has multiple contours, stop after reversing the last
1634 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001636 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 break;
1638 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001639 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001641 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001642 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001643 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001645 this->cubicTo(pts[2], pts[1], pts[0]);
1646 break;
1647 case kClose_Verb:
1648 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
1650 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001651 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 break;
1653 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 }
1655}
1656
reed@google.com63d73742012-01-10 15:33:12 +00001657void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001658 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 const SkPoint* pts = src.fPathRef->pointsEnd();
1661 // we will iterator through src's verbs backwards
1662 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1663 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001664 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001665
1666 bool needMove = true;
1667 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 while (verbs < verbsEnd) {
1669 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001670 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001671
1672 if (needMove) {
1673 --pts;
1674 this->moveTo(pts->fX, pts->fY);
1675 needMove = false;
1676 }
1677 pts -= n;
1678 switch (v) {
1679 case kMove_Verb:
1680 if (needClose) {
1681 this->close();
1682 needClose = false;
1683 }
1684 needMove = true;
1685 pts += 1; // so we see the point in "if (needMove)" above
1686 break;
1687 case kLine_Verb:
1688 this->lineTo(pts[0]);
1689 break;
1690 case kQuad_Verb:
1691 this->quadTo(pts[1], pts[0]);
1692 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001693 case kConic_Verb:
1694 this->conicTo(pts[1], pts[0], *--conicWeights);
1695 break;
reed@google.com63d73742012-01-10 15:33:12 +00001696 case kCubic_Verb:
1697 this->cubicTo(pts[2], pts[1], pts[0]);
1698 break;
1699 case kClose_Verb:
1700 needClose = true;
1701 break;
1702 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001703 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001704 }
1705 }
1706}
1707
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708///////////////////////////////////////////////////////////////////////////////
1709
1710void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1711 SkMatrix matrix;
1712
1713 matrix.setTranslate(dx, dy);
1714 this->transform(matrix, dst);
1715}
1716
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1718 int level = 2) {
1719 if (--level >= 0) {
1720 SkPoint tmp[7];
1721
1722 SkChopCubicAtHalf(pts, tmp);
1723 subdivide_cubic_to(path, &tmp[0], level);
1724 subdivide_cubic_to(path, &tmp[3], level);
1725 } else {
1726 path->cubicTo(pts[1], pts[2], pts[3]);
1727 }
1728}
1729
1730void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1731 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001732 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 dst = (SkPath*)this;
1734 }
1735
tomhudson@google.com8d430182011-06-06 19:11:19 +00001736 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 SkPath tmp;
1738 tmp.fFillType = fFillType;
1739
1740 SkPath::Iter iter(*this, false);
1741 SkPoint pts[4];
1742 SkPath::Verb verb;
1743
reed@google.com4a3b7142012-05-16 17:16:46 +00001744 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 switch (verb) {
1746 case kMove_Verb:
1747 tmp.moveTo(pts[0]);
1748 break;
1749 case kLine_Verb:
1750 tmp.lineTo(pts[1]);
1751 break;
1752 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001753 // promote the quad to a conic
1754 tmp.conicTo(pts[1], pts[2],
1755 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001757 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001758 tmp.conicTo(pts[1], pts[2],
1759 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001760 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 case kCubic_Verb:
1762 subdivide_cubic_to(&tmp, pts);
1763 break;
1764 case kClose_Verb:
1765 tmp.close();
1766 break;
1767 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001768 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 break;
1770 }
1771 }
1772
1773 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001774 SkPathRef::Editor ed(&dst->fPathRef);
1775 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001776 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1779
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001782 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001783 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001785
reed026beb52015-06-10 14:23:15 -07001786 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1787 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001788 } else {
1789 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001790 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1791 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001793 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1794 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001795 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001796 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001797 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001798 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001799 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001800 }
1801 }
1802
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803 SkDEBUGCODE(dst->validate();)
1804 }
1805}
1806
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807///////////////////////////////////////////////////////////////////////////////
1808///////////////////////////////////////////////////////////////////////////////
1809
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001811 kEmptyContour_SegmentState, // The current contour is empty. We may be
1812 // starting processing or we may have just
1813 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1815 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1816 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817};
1818
1819SkPath::Iter::Iter() {
1820#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001821 fPts = nullptr;
1822 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001825 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826#endif
1827 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001828 fVerbs = nullptr;
1829 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 fNeedClose = false;
1831}
1832
1833SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1834 this->setPath(path, forceClose);
1835}
1836
1837void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001838 fPts = path.fPathRef->points();
1839 fVerbs = path.fPathRef->verbs();
1840 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001841 fConicWeights = path.fPathRef->conicWeights();
1842 if (fConicWeights) {
1843 fConicWeights -= 1; // begin one behind
1844 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001846 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 fForceClose = SkToU8(forceClose);
1848 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001849 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850}
1851
1852bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001853 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 return false;
1855 }
1856 if (fForceClose) {
1857 return true;
1858 }
1859
1860 const uint8_t* verbs = fVerbs;
1861 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001862
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 if (kMove_Verb == *(verbs - 1)) {
1864 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 }
1866
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 while (verbs > stop) {
1868 // verbs points one beyond the current verb, decrement first.
1869 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 if (kMove_Verb == v) {
1871 break;
1872 }
1873 if (kClose_Verb == v) {
1874 return true;
1875 }
1876 }
1877 return false;
1878}
1879
1880SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001881 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001883 // A special case: if both points are NaN, SkPoint::operation== returns
1884 // false, but the iterator expects that they are treated as the same.
1885 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001886 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1887 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001888 return kClose_Verb;
1889 }
1890
reed@google.com9e25dbf2012-05-15 17:05:38 +00001891 pts[0] = fLastPt;
1892 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 fLastPt = fMoveTo;
1894 fCloseLine = true;
1895 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001896 } else {
1897 pts[0] = fMoveTo;
1898 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900}
1901
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 if (fSegmentState == kAfterMove_SegmentState) {
1904 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001906 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1909 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912}
1913
caryclarke8c56662015-07-14 11:19:26 -07001914void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 // We need to step over anything that will not move the current draw point
1916 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001917 const uint8_t* lastMoveVerb = nullptr;
1918 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001919 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 SkPoint lastPt = fLastPt;
1921 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 switch (verb) {
1924 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 // Keep a record of this most recent move
1926 lastMoveVerb = fVerbs;
1927 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001928 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001929 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 fPts++;
1932 break;
1933
1934 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001935 // A close when we are in a segment is always valid except when it
1936 // follows a move which follows a segment.
1937 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 return;
1939 }
1940 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001941 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 break;
1943
1944 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001945 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 if (lastMoveVerb) {
1947 fVerbs = lastMoveVerb;
1948 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001949 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 return;
1951 }
1952 return;
1953 }
1954 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 fPts++;
1957 break;
1958
reed@google.com277c3f82013-05-31 15:17:50 +00001959 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001961 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 if (lastMoveVerb) {
1963 fVerbs = lastMoveVerb;
1964 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001965 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 return;
1967 }
1968 return;
1969 }
1970 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001973 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 break;
1975
1976 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001977 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 if (lastMoveVerb) {
1979 fVerbs = lastMoveVerb;
1980 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001981 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 return;
1983 }
1984 return;
1985 }
1986 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 fPts += 3;
1989 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001990
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001992 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 }
1994 }
1995}
1996
reed@google.com4a3b7142012-05-16 17:16:46 +00001997SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001998 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001999
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002001 // Close the curve if requested and if there is some curve to close
2002 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002003 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 return kLine_Verb;
2005 }
2006 fNeedClose = false;
2007 return kClose_Verb;
2008 }
2009 return kDone_Verb;
2010 }
2011
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 // fVerbs is one beyond the current verb, decrement first
2013 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002014 const SkPoint* SK_RESTRICT srcPts = fPts;
2015 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016
2017 switch (verb) {
2018 case kMove_Verb:
2019 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002020 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021 verb = this->autoClose(pts);
2022 if (verb == kClose_Verb) {
2023 fNeedClose = false;
2024 }
2025 return (Verb)verb;
2026 }
2027 if (fVerbs == fVerbStop) { // might be a trailing moveto
2028 return kDone_Verb;
2029 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002030 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002031 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002033 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002034 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 fNeedClose = fForceClose;
2036 break;
2037 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002038 pts[0] = this->cons_moveTo();
2039 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 fLastPt = srcPts[0];
2041 fCloseLine = false;
2042 srcPts += 1;
2043 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002044 case kConic_Verb:
2045 fConicWeights += 1;
2046 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002048 pts[0] = this->cons_moveTo();
2049 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 fLastPt = srcPts[1];
2051 srcPts += 2;
2052 break;
2053 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002054 pts[0] = this->cons_moveTo();
2055 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 fLastPt = srcPts[2];
2057 srcPts += 3;
2058 break;
2059 case kClose_Verb:
2060 verb = this->autoClose(pts);
2061 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002062 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 } else {
2064 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002065 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002067 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068 break;
2069 }
2070 fPts = srcPts;
2071 return (Verb)verb;
2072}
2073
2074///////////////////////////////////////////////////////////////////////////////
2075
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002077 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078*/
2079
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002080size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2081 SkRect oval;
2082 SkRRect rrect;
2083 bool isCCW;
2084 unsigned start;
2085 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2086 rrect.setOval(oval);
2087 // Convert to rrect start indices.
2088 start *= 2;
2089 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2090 return false;
2091 }
2092 if (!storage) {
2093 // packed header, rrect, start index.
2094 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002095 }
2096
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002097 SkWBuffer buffer(storage);
2098 // Rewrite header's first direction based on rrect direction.
2099 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2100 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2101 packedHeader |= firstDir << kDirection_SerializationShift;
2102 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2103 buffer.write32(packedHeader);
2104 rrect.writeToBuffer(&buffer);
2105 buffer.write32(SkToS32(start));
2106 buffer.padToAlign4();
2107 return buffer.pos();
2108}
2109
2110size_t SkPath::writeToMemory(void* storage) const {
2111 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002112
robertphillips@google.com466310d2013-12-03 16:43:54 +00002113 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002115 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002116 (fIsVolatile << kIsVolatile_SerializationShift) |
2117 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002118 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2119 return bytes;
2120 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002121
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002122 SkWBuffer buffer(storage);
2123
2124 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2125 if (nullptr == storage) {
2126 // packed header, pathref, start index
2127 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2128 return SkAlign4(byteCount);
2129 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002130 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002131 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002132
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002133 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002134
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002135 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002136 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137}
2138
Mike Reed41a930f2017-07-26 17:33:44 -04002139sk_sp<SkData> SkPath::serialize() const {
2140 size_t size = this->writeToMemory(nullptr);
2141 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2142 this->writeToMemory(data->writable_data());
2143 return data;
2144}
2145
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002146size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002147 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002148
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002149 int32_t packed;
2150 if (!buffer.readS32(&packed)) {
2151 return 0;
2152 }
2153
reed8f086022015-06-11 14:22:19 -07002154 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002155 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2156 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2157 if (version >= kPathPrivTypeEnumVersion) {
2158 SerializationType type =
2159 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2160 switch (type) {
2161 case SerializationType::kRRect: {
2162 Direction rrectDir;
2163 SkRRect rrect;
2164 int32_t start;
2165 switch (dir) {
2166 case SkPathPriv::kCW_FirstDirection:
2167 rrectDir = kCW_Direction;
2168 break;
2169 case SkPathPriv::kCCW_FirstDirection:
2170 rrectDir = kCCW_Direction;
2171 break;
2172 default:
2173 return 0;
2174 }
2175 if (!rrect.readFromBuffer(&buffer)) {
2176 return 0;
2177 }
2178 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2179 return 0;
2180 }
2181 this->reset();
2182 this->addRRect(rrect, rrectDir, SkToUInt(start));
2183 this->setFillType(fillType);
2184 buffer.skipToAlign4();
2185 return buffer.pos();
2186 }
2187 case SerializationType::kGeneral:
2188 // Fall through to general path deserialization
2189 break;
2190 default:
2191 return 0;
2192 }
2193 }
caryclark69006412016-02-17 12:16:27 -08002194 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2195 return 0;
2196 }
mtklein1b249332015-07-07 12:21:21 -07002197
Brian Salomon7072e222017-11-29 15:12:45 -05002198 // These are written into the serialized data but we no longer use them in the deserialized
2199 // path. If convexity is corrupted it may cause the GPU backend to make incorrect
2200 // rendering choices, possibly crashing. We set them to unknown so that they'll be recomputed if
2201 // requested.
2202 fConvexity = kUnknown_Convexity;
2203 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2204
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002205 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002206 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002207 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002208 if (!pathRef) {
2209 return 0;
2210 }
2211
2212 fPathRef.reset(pathRef);
2213 SkDEBUGCODE(this->validate();)
2214 buffer.skipToAlign4();
ajumaf8aec582016-01-13 13:46:31 -08002215 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002216}
2217
2218///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002219
Ben Wagner4d1955c2017-03-10 13:08:15 -05002220#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002221#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002222#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002223
reed@google.com51bbe752013-01-17 22:07:50 +00002224static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002225 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002226 str->append(label);
2227 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002228
reed@google.com51bbe752013-01-17 22:07:50 +00002229 const SkScalar* values = &pts[0].fX;
2230 count *= 2;
2231
2232 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002233 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002234 if (i < count - 1) {
2235 str->append(", ");
2236 }
2237 }
Mike Reed405b9d22018-01-09 15:11:08 -05002238 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002239 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002240 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002241 }
caryclark08fa28c2014-10-23 13:08:56 -07002242 str->append(");");
reede05fed02014-12-15 07:59:53 -08002243 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002244 str->append(" // ");
2245 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002246 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002247 if (i < count - 1) {
2248 str->append(", ");
2249 }
2250 }
2251 if (conicWeight >= 0) {
2252 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002253 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002254 }
2255 }
2256 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002257}
2258
caryclarke9562592014-09-15 09:26:09 -07002259void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002260 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002261 Iter iter(*this, forceClose);
2262 SkPoint pts[4];
2263 Verb verb;
2264
reed@google.com51bbe752013-01-17 22:07:50 +00002265 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002266 char const * const gFillTypeStrs[] = {
2267 "Winding",
2268 "EvenOdd",
2269 "InverseWinding",
2270 "InverseEvenOdd",
2271 };
2272 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2273 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002274 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002275 switch (verb) {
2276 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002277 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002278 break;
2279 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002280 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002281 break;
2282 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002283 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002284 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002285 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002286 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002287 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002288 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002289 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002290 break;
2291 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002292 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002293 break;
2294 default:
2295 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2296 verb = kDone_Verb; // stop the loop
2297 break;
2298 }
caryclark1049f122015-04-20 08:31:59 -07002299 if (!wStream && builder.size()) {
2300 SkDebugf("%s", builder.c_str());
2301 builder.reset();
2302 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002303 }
caryclark66a5d8b2014-06-24 08:30:15 -07002304 if (wStream) {
2305 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002306 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002307}
2308
reed@android.come522ca52009-11-23 20:10:41 +00002309void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002310 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002311}
2312
2313void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002314 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002315}
2316
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002317
Cary Clark0461e9f2017-08-25 15:13:38 -04002318bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002319 if ((fFillType & ~3) != 0) {
2320 return false;
2321 }
reed@google.comabf15c12011-01-18 20:35:51 +00002322
djsollen@google.com077348c2012-10-22 20:23:32 +00002323#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002324 if (!fBoundsIsDirty) {
2325 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002326
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002327 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002328 if (SkToBool(fIsFinite) != isFinite) {
2329 return false;
2330 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002331
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002332 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002333 // if we're empty, fBounds may be empty but translated, so we can't
2334 // necessarily compare to bounds directly
2335 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2336 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002337 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2338 return false;
2339 }
reed@android.come522ca52009-11-23 20:10:41 +00002340 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002341 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002342 if (!fBounds.isEmpty()) {
2343 return false;
2344 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002345 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002346 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002347 if (!fBounds.contains(bounds)) {
2348 return false;
2349 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002350 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002351 }
reed@android.come522ca52009-11-23 20:10:41 +00002352 }
2353 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002354#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002355 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002356}
reed@android.come522ca52009-11-23 20:10:41 +00002357
reed@google.com04863fa2011-05-15 04:08:24 +00002358///////////////////////////////////////////////////////////////////////////////
2359
reed@google.com0b7b9822011-05-16 12:29:27 +00002360static int sign(SkScalar x) { return x < 0; }
2361#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002362
robertphillipsc506e302014-09-16 09:43:31 -07002363enum DirChange {
2364 kLeft_DirChange,
2365 kRight_DirChange,
2366 kStraight_DirChange,
2367 kBackwards_DirChange,
2368
2369 kInvalid_DirChange
2370};
2371
2372
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002373static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002374 // The error epsilon was empirically derived; worse case round rects
2375 // with a mid point outset by 2x float epsilon in tests had an error
2376 // of 12.
2377 const int epsilon = 16;
2378 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2379 return false;
2380 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002381 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002382 int aBits = SkFloatAs2sCompliment(compA);
2383 int bBits = SkFloatAs2sCompliment(compB);
2384 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002385}
2386
caryclarkb4216502015-03-02 13:02:34 -08002387static bool approximately_zero_when_compared_to(double x, double y) {
2388 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002389}
2390
caryclarkb4216502015-03-02 13:02:34 -08002391
reed@google.com04863fa2011-05-15 04:08:24 +00002392// only valid for a single contour
2393struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002394 Convexicator()
2395 : fPtCount(0)
2396 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002397 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002398 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002399 , fIsCurve(false)
2400 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002401 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 // warnings
djsollen2f124632016-04-29 13:53:05 -07002403 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002404 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002405 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002406 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002407 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002408
2409 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002410 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002411 }
2412
2413 SkPath::Convexity getConvexity() const { return fConvexity; }
2414
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002415 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002416 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002417
reed@google.com04863fa2011-05-15 04:08:24 +00002418 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002419 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002420 return;
2421 }
2422
2423 if (0 == fPtCount) {
2424 fCurrPt = pt;
2425 ++fPtCount;
2426 } else {
2427 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002428 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002429 if (!SkScalarIsFinite(lengthSqd)) {
2430 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002431 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002432 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002433 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002434 fCurrPt = pt;
2435 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002436 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002437 } else {
2438 SkASSERT(fPtCount > 2);
2439 this->addVec(vec);
2440 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002441
reed@google.com85b6e392011-05-15 20:25:17 +00002442 int sx = sign(vec.fX);
2443 int sy = sign(vec.fY);
2444 fDx += (sx != fSx);
2445 fDy += (sy != fSy);
2446 fSx = sx;
2447 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002448
reed@google.com85b6e392011-05-15 20:25:17 +00002449 if (fDx > 3 || fDy > 3) {
2450 fConvexity = SkPath::kConcave_Convexity;
2451 }
reed@google.com04863fa2011-05-15 04:08:24 +00002452 }
2453 }
2454 }
2455
2456 void close() {
2457 if (fPtCount > 2) {
2458 this->addVec(fFirstVec);
2459 }
2460 }
2461
caryclarkb4216502015-03-02 13:02:34 -08002462 DirChange directionChange(const SkVector& curVec) {
2463 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2464
2465 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2466 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2467 largest = SkTMax(largest, -smallest);
2468
2469 if (!almost_equal(largest, largest + cross)) {
2470 int sign = SkScalarSignAsInt(cross);
2471 if (sign) {
2472 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2473 }
2474 }
2475
2476 if (cross) {
2477 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2478 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2479 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2480 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2481 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2482 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2483 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2484 if (sign) {
2485 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2486 }
2487 }
2488 }
2489
Cary Clarkdf429f32017-11-08 11:44:31 -05002490 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2491 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2492 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2493 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002494 fLastVec.dot(curVec) < 0.0f) {
2495 return kBackwards_DirChange;
2496 }
2497
2498 return kStraight_DirChange;
2499 }
2500
Cary Clarkc8323aa2017-08-25 08:04:43 -04002501 bool hasBackwards() const {
2502 return fBackwards;
2503 }
caryclarkb4216502015-03-02 13:02:34 -08002504
caryclarkd3d1a982014-12-08 04:57:38 -08002505 bool isFinite() const {
2506 return fIsFinite;
2507 }
2508
caryclark5ccef572015-03-02 10:07:56 -08002509 void setCurve(bool isCurve) {
2510 fIsCurve = isCurve;
2511 }
2512
reed@google.com04863fa2011-05-15 04:08:24 +00002513private:
2514 void addVec(const SkVector& vec) {
2515 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002516 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002517 switch (dir) {
2518 case kLeft_DirChange: // fall through
2519 case kRight_DirChange:
2520 if (kInvalid_DirChange == fExpectedDir) {
2521 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002522 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2523 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002524 } else if (dir != fExpectedDir) {
2525 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002526 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002527 }
2528 fLastVec = vec;
2529 break;
2530 case kStraight_DirChange:
2531 break;
2532 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002533 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002534 // If any of the subsequent dir is non-backward, it'll be concave.
2535 // Otherwise, it's still convex.
2536 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002537 }
robertphillipsc506e302014-09-16 09:43:31 -07002538 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002539 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002540 break;
2541 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002542 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002543 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002544 }
2545 }
2546
caryclarkb4216502015-03-02 13:02:34 -08002547 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002548 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002549 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002550 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2551 // value with the current vec is deemed to be of a significant value.
2552 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002553 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002554 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002555 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002556 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002557 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002558 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002559 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002560 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002561};
2562
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002563SkPath::Convexity SkPath::internalGetConvexity() const {
2564 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002565 SkPoint pts[4];
2566 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002567 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002568
2569 int contourCount = 0;
2570 int count;
2571 Convexicator state;
2572
caryclarkd3d1a982014-12-08 04:57:38 -08002573 if (!isFinite()) {
2574 return kUnknown_Convexity;
2575 }
Brian Osman205a1262017-09-18 13:13:48 +00002576 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002577 switch (verb) {
2578 case kMove_Verb:
2579 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002580 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002581 return kConcave_Convexity;
2582 }
2583 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002584 // fall through
2585 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002586 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002587 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002588 break;
caryclark5ccef572015-03-02 10:07:56 -08002589 case kQuad_Verb:
2590 // fall through
2591 case kConic_Verb:
2592 // fall through
2593 case kCubic_Verb:
2594 count = 2 + (kCubic_Verb == verb);
2595 // As an additional enhancement, this could set curve true only
2596 // if the curve is nonlinear
2597 state.setCurve(true);
2598 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002599 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002600 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002601 state.close();
2602 count = 0;
2603 break;
2604 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002605 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002606 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002607 return kConcave_Convexity;
2608 }
2609
2610 for (int i = 1; i <= count; i++) {
2611 state.addPt(pts[i]);
2612 }
2613 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002614 if (!state.isFinite()) {
2615 return kUnknown_Convexity;
2616 }
reed@google.com04863fa2011-05-15 04:08:24 +00002617 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002618 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002619 return kConcave_Convexity;
2620 }
2621 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002622 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002623 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002624 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2625 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2626 fConvexity = Convexity::kConcave_Convexity;
2627 } else {
2628 fFirstDirection = state.getFirstDirection();
2629 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002630 }
2631 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002632}
reed@google.com69a99432012-01-10 18:00:10 +00002633
2634///////////////////////////////////////////////////////////////////////////////
2635
2636class ContourIter {
2637public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002638 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002639
2640 bool done() const { return fDone; }
2641 // if !done() then these may be called
2642 int count() const { return fCurrPtCount; }
2643 const SkPoint* pts() const { return fCurrPt; }
2644 void next();
2645
2646private:
2647 int fCurrPtCount;
2648 const SkPoint* fCurrPt;
2649 const uint8_t* fCurrVerb;
2650 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002651 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002652 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002653 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002654};
2655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002656ContourIter::ContourIter(const SkPathRef& pathRef) {
2657 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002658 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002659 fCurrPt = pathRef.points();
2660 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002661 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002662 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002663 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002664 this->next();
2665}
2666
2667void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002668 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002669 fDone = true;
2670 }
2671 if (fDone) {
2672 return;
2673 }
2674
2675 // skip pts of prev contour
2676 fCurrPt += fCurrPtCount;
2677
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002678 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002679 int ptCount = 1; // moveTo
2680 const uint8_t* verbs = fCurrVerb;
2681
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002682 for (--verbs; verbs > fStopVerbs; --verbs) {
2683 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002684 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002685 goto CONTOUR_END;
2686 case SkPath::kLine_Verb:
2687 ptCount += 1;
2688 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002689 case SkPath::kConic_Verb:
2690 fCurrConicWeight += 1;
2691 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002692 case SkPath::kQuad_Verb:
2693 ptCount += 2;
2694 break;
2695 case SkPath::kCubic_Verb:
2696 ptCount += 3;
2697 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002698 case SkPath::kClose_Verb:
2699 break;
2700 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002701 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002702 break;
2703 }
2704 }
2705CONTOUR_END:
2706 fCurrPtCount = ptCount;
2707 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002708 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002709}
2710
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002711// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002712static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002713 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2714 // We may get 0 when the above subtracts underflow. We expect this to be
2715 // very rare and lazily promote to double.
2716 if (0 == cross) {
2717 double p0x = SkScalarToDouble(p0.fX);
2718 double p0y = SkScalarToDouble(p0.fY);
2719
2720 double p1x = SkScalarToDouble(p1.fX);
2721 double p1y = SkScalarToDouble(p1.fY);
2722
2723 double p2x = SkScalarToDouble(p2.fX);
2724 double p2y = SkScalarToDouble(p2.fY);
2725
2726 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2727 (p1y - p0y) * (p2x - p0x));
2728
2729 }
2730 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002731}
2732
reed@google.comc1ea60a2012-01-31 15:15:36 +00002733// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002734static int find_max_y(const SkPoint pts[], int count) {
2735 SkASSERT(count > 0);
2736 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002737 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002738 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002739 SkScalar y = pts[i].fY;
2740 if (y > max) {
2741 max = y;
2742 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002743 }
2744 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002745 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002746}
2747
reed@google.comcabaf1d2012-01-11 21:03:05 +00002748static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2749 int i = index;
2750 for (;;) {
2751 i = (i + inc) % n;
2752 if (i == index) { // we wrapped around, so abort
2753 break;
2754 }
2755 if (pts[index] != pts[i]) { // found a different point, success!
2756 break;
2757 }
2758 }
2759 return i;
2760}
2761
reed@google.comc1ea60a2012-01-31 15:15:36 +00002762/**
2763 * Starting at index, and moving forward (incrementing), find the xmin and
2764 * xmax of the contiguous points that have the same Y.
2765 */
2766static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2767 int* maxIndexPtr) {
2768 const SkScalar y = pts[index].fY;
2769 SkScalar min = pts[index].fX;
2770 SkScalar max = min;
2771 int minIndex = index;
2772 int maxIndex = index;
2773 for (int i = index + 1; i < n; ++i) {
2774 if (pts[i].fY != y) {
2775 break;
2776 }
2777 SkScalar x = pts[i].fX;
2778 if (x < min) {
2779 min = x;
2780 minIndex = i;
2781 } else if (x > max) {
2782 max = x;
2783 maxIndex = i;
2784 }
2785 }
2786 *maxIndexPtr = maxIndex;
2787 return minIndex;
2788}
2789
reed026beb52015-06-10 14:23:15 -07002790static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2791 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002792}
2793
reed@google.comac8543f2012-01-30 20:51:25 +00002794/*
2795 * We loop through all contours, and keep the computed cross-product of the
2796 * contour that contained the global y-max. If we just look at the first
2797 * contour, we may find one that is wound the opposite way (correctly) since
2798 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2799 * that is outer most (or at least has the global y-max) before we can consider
2800 * its cross product.
2801 */
reed026beb52015-06-10 14:23:15 -07002802bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002803 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2804 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002805 return true;
2806 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002807
2808 // don't want to pay the cost for computing this if it
2809 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002810 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2811 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002812 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002813 return false;
2814 }
reed@google.com69a99432012-01-10 18:00:10 +00002815
reed026beb52015-06-10 14:23:15 -07002816 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002817
reed@google.comac8543f2012-01-30 20:51:25 +00002818 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002819 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002820 SkScalar ymaxCross = 0;
2821
reed@google.com69a99432012-01-10 18:00:10 +00002822 for (; !iter.done(); iter.next()) {
2823 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002824 if (n < 3) {
2825 continue;
2826 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002827
reed@google.comcabaf1d2012-01-11 21:03:05 +00002828 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002829 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002830 int index = find_max_y(pts, n);
2831 if (pts[index].fY < ymax) {
2832 continue;
2833 }
2834
2835 // If there is more than 1 distinct point at the y-max, we take the
2836 // x-min and x-max of them and just subtract to compute the dir.
2837 if (pts[(index + 1) % n].fY == pts[index].fY) {
2838 int maxIndex;
2839 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2840 if (minIndex == maxIndex) {
2841 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002842 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002843 SkASSERT(pts[minIndex].fY == pts[index].fY);
2844 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2845 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2846 // we just subtract the indices, and let that auto-convert to
2847 // SkScalar, since we just want - or + to signal the direction.
2848 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002849 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002850 TRY_CROSSPROD:
2851 // Find a next and prev index to use for the cross-product test,
2852 // but we try to find pts that form non-zero vectors from pts[index]
2853 //
2854 // Its possible that we can't find two non-degenerate vectors, so
2855 // we have to guard our search (e.g. all the pts could be in the
2856 // same place).
2857
2858 // we pass n - 1 instead of -1 so we don't foul up % operator by
2859 // passing it a negative LH argument.
2860 int prev = find_diff_pt(pts, index, n, n - 1);
2861 if (prev == index) {
2862 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002863 continue;
2864 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002865 int next = find_diff_pt(pts, index, n, 1);
2866 SkASSERT(next != index);
2867 cross = cross_prod(pts[prev], pts[index], pts[next]);
2868 // if we get a zero and the points are horizontal, then we look at the spread in
2869 // x-direction. We really should continue to walk away from the degeneracy until
2870 // there is a divergence.
2871 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2872 // construct the subtract so we get the correct Direction below
2873 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002874 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002875 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002876
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002877 if (cross) {
2878 // record our best guess so far
2879 ymax = pts[index].fY;
2880 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002881 }
2882 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002883 if (ymaxCross) {
2884 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002885 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002886 return true;
2887 } else {
2888 return false;
2889 }
reed@google.comac8543f2012-01-30 20:51:25 +00002890}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891
2892///////////////////////////////////////////////////////////////////////////////
2893
caryclark9aacd902015-12-14 08:38:09 -08002894static bool between(SkScalar a, SkScalar b, SkScalar c) {
2895 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2896 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2897 return (a - b) * (c - b) <= 0;
2898}
2899
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002900static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2901 SkScalar t) {
2902 SkScalar A = c3 + 3*(c1 - c2) - c0;
2903 SkScalar B = 3*(c2 - c1 - c1 + c0);
2904 SkScalar C = 3*(c1 - c0);
2905 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002906 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907}
2908
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002909template <size_t N> static void find_minmax(const SkPoint pts[],
2910 SkScalar* minPtr, SkScalar* maxPtr) {
2911 SkScalar min, max;
2912 min = max = pts[0].fX;
2913 for (size_t i = 1; i < N; ++i) {
2914 min = SkMinScalar(min, pts[i].fX);
2915 max = SkMaxScalar(max, pts[i].fX);
2916 }
2917 *minPtr = min;
2918 *maxPtr = max;
2919}
2920
caryclark9cb5d752015-12-18 04:35:24 -08002921static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2922 if (start.fY == end.fY) {
2923 return between(start.fX, x, end.fX) && x != end.fX;
2924 } else {
2925 return x == start.fX && y == start.fY;
2926 }
2927}
2928
caryclark9aacd902015-12-14 08:38:09 -08002929static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002930 SkScalar y0 = pts[0].fY;
2931 SkScalar y3 = pts[3].fY;
2932
2933 int dir = 1;
2934 if (y0 > y3) {
2935 SkTSwap(y0, y3);
2936 dir = -1;
2937 }
2938 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 return 0;
2940 }
caryclark9cb5d752015-12-18 04:35:24 -08002941 if (checkOnCurve(x, y, pts[0], pts[3])) {
2942 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002943 return 0;
2944 }
caryclark9cb5d752015-12-18 04:35:24 -08002945 if (y == y3) {
2946 return 0;
2947 }
2948
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 // quickreject or quickaccept
2950 SkScalar min, max;
2951 find_minmax<4>(pts, &min, &max);
2952 if (x < min) {
2953 return 0;
2954 }
2955 if (x > max) {
2956 return dir;
2957 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002958
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002960 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002961 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002962 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002963 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002964 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002965 if (SkScalarNearlyEqual(xt, x)) {
2966 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2967 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002968 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002969 }
2970 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 return xt < x ? dir : 0;
2972}
2973
caryclark9aacd902015-12-14 08:38:09 -08002974static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 SkPoint dst[10];
2976 int n = SkChopCubicAtYExtrema(pts, dst);
2977 int w = 0;
2978 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002979 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 }
2981 return w;
2982}
2983
caryclark9aacd902015-12-14 08:38:09 -08002984static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2985 SkASSERT(src);
2986 SkASSERT(t >= 0 && t <= 1);
2987 SkScalar src2w = src[2] * w;
2988 SkScalar C = src[0];
2989 SkScalar A = src[4] - 2 * src2w + C;
2990 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002991 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002992}
2993
2994
2995static double conic_eval_denominator(SkScalar w, SkScalar t) {
2996 SkScalar B = 2 * (w - 1);
2997 SkScalar C = 1;
2998 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002999 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003000}
3001
3002static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
3003 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 SkScalar y0 = pts[0].fY;
3005 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003006
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 int dir = 1;
3008 if (y0 > y2) {
3009 SkTSwap(y0, y2);
3010 dir = -1;
3011 }
caryclark9aacd902015-12-14 08:38:09 -08003012 if (y < y0 || y > y2) {
3013 return 0;
3014 }
caryclark9cb5d752015-12-18 04:35:24 -08003015 if (checkOnCurve(x, y, pts[0], pts[2])) {
3016 *onCurveCount += 1;
3017 return 0;
3018 }
caryclark9aacd902015-12-14 08:38:09 -08003019 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 return 0;
3021 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003022
caryclark9aacd902015-12-14 08:38:09 -08003023 SkScalar roots[2];
3024 SkScalar A = pts[2].fY;
3025 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3026 SkScalar C = pts[0].fY;
3027 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3028 B -= C; // B = b*w - w * yCept + yCept - a
3029 C -= y;
3030 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3031 SkASSERT(n <= 1);
3032 SkScalar xt;
3033 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003034 // zero roots are returned only when y0 == y
3035 // Need [0] if dir == 1
3036 // and [2] if dir == -1
3037 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003038 } else {
3039 SkScalar t = roots[0];
3040 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3041 }
3042 if (SkScalarNearlyEqual(xt, x)) {
3043 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3044 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003045 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003046 }
3047 }
3048 return xt < x ? dir : 0;
3049}
3050
3051static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3052 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3053 if (y0 == y1) {
3054 return true;
3055 }
3056 if (y0 < y1) {
3057 return y1 <= y2;
3058 } else {
3059 return y1 >= y2;
3060 }
3061}
3062
3063static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3064 int* onCurveCount) {
3065 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003066 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003067 // If the data points are very large, the conic may not be monotonic but may also
3068 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003069 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3070 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3071 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003072 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3073 }
3074 return w;
3075}
3076
3077static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3078 SkScalar y0 = pts[0].fY;
3079 SkScalar y2 = pts[2].fY;
3080
3081 int dir = 1;
3082 if (y0 > y2) {
3083 SkTSwap(y0, y2);
3084 dir = -1;
3085 }
3086 if (y < y0 || y > y2) {
3087 return 0;
3088 }
caryclark9cb5d752015-12-18 04:35:24 -08003089 if (checkOnCurve(x, y, pts[0], pts[2])) {
3090 *onCurveCount += 1;
3091 return 0;
3092 }
caryclark9aacd902015-12-14 08:38:09 -08003093 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003094 return 0;
3095 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003096 // bounds check on X (not required. is it faster?)
3097#if 0
3098 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3099 return 0;
3100 }
3101#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003102
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003103 SkScalar roots[2];
3104 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3105 2 * (pts[1].fY - pts[0].fY),
3106 pts[0].fY - y,
3107 roots);
3108 SkASSERT(n <= 1);
3109 SkScalar xt;
3110 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003111 // zero roots are returned only when y0 == y
3112 // Need [0] if dir == 1
3113 // and [2] if dir == -1
3114 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003115 } else {
3116 SkScalar t = roots[0];
3117 SkScalar C = pts[0].fX;
3118 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3119 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003120 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003121 }
caryclark9aacd902015-12-14 08:38:09 -08003122 if (SkScalarNearlyEqual(xt, x)) {
3123 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3124 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003125 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003126 }
3127 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003128 return xt < x ? dir : 0;
3129}
3130
caryclark9aacd902015-12-14 08:38:09 -08003131static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003132 SkPoint dst[5];
3133 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003134
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003135 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3136 n = SkChopQuadAtYExtrema(pts, dst);
3137 pts = dst;
3138 }
caryclark9aacd902015-12-14 08:38:09 -08003139 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003140 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003141 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003142 }
3143 return w;
3144}
3145
caryclark9aacd902015-12-14 08:38:09 -08003146static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003147 SkScalar x0 = pts[0].fX;
3148 SkScalar y0 = pts[0].fY;
3149 SkScalar x1 = pts[1].fX;
3150 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003151
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003152 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003153
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003154 int dir = 1;
3155 if (y0 > y1) {
3156 SkTSwap(y0, y1);
3157 dir = -1;
3158 }
caryclark9aacd902015-12-14 08:38:09 -08003159 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160 return 0;
3161 }
caryclark9cb5d752015-12-18 04:35:24 -08003162 if (checkOnCurve(x, y, pts[0], pts[1])) {
3163 *onCurveCount += 1;
3164 return 0;
3165 }
3166 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003167 return 0;
3168 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003169 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003170
caryclark9aacd902015-12-14 08:38:09 -08003171 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003172 // zero cross means the point is on the line, and since the case where
3173 // y of the query point is at the end point is handled above, we can be
3174 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003175 if (x != x1 || y != pts[1].fY) {
3176 *onCurveCount += 1;
3177 }
caryclark9aacd902015-12-14 08:38:09 -08003178 dir = 0;
3179 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 dir = 0;
3181 }
3182 return dir;
3183}
3184
caryclark9aacd902015-12-14 08:38:09 -08003185static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3186 SkTDArray<SkVector>* tangents) {
3187 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3188 && !between(pts[2].fY, y, pts[3].fY)) {
3189 return;
3190 }
3191 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3192 && !between(pts[2].fX, x, pts[3].fX)) {
3193 return;
3194 }
3195 SkPoint dst[10];
3196 int n = SkChopCubicAtYExtrema(pts, dst);
3197 for (int i = 0; i <= n; ++i) {
3198 SkPoint* c = &dst[i * 3];
3199 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003200 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003201 continue;
mbarbella276e6332016-05-31 14:44:01 -07003202 }
caryclark9aacd902015-12-14 08:38:09 -08003203 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3204 if (!SkScalarNearlyEqual(x, xt)) {
3205 continue;
3206 }
3207 SkVector tangent;
3208 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3209 tangents->push(tangent);
3210 }
3211}
3212
3213static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3214 SkTDArray<SkVector>* tangents) {
3215 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3216 return;
3217 }
3218 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3219 return;
3220 }
3221 SkScalar roots[2];
3222 SkScalar A = pts[2].fY;
3223 SkScalar B = pts[1].fY * w - y * w + y;
3224 SkScalar C = pts[0].fY;
3225 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3226 B -= C; // B = b*w - w * yCept + yCept - a
3227 C -= y;
3228 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3229 for (int index = 0; index < n; ++index) {
3230 SkScalar t = roots[index];
3231 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3232 if (!SkScalarNearlyEqual(x, xt)) {
3233 continue;
3234 }
3235 SkConic conic(pts, w);
3236 tangents->push(conic.evalTangentAt(t));
3237 }
3238}
3239
3240static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3241 SkTDArray<SkVector>* tangents) {
3242 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3243 return;
3244 }
3245 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3246 return;
3247 }
3248 SkScalar roots[2];
3249 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3250 2 * (pts[1].fY - pts[0].fY),
3251 pts[0].fY - y,
3252 roots);
3253 for (int index = 0; index < n; ++index) {
3254 SkScalar t = roots[index];
3255 SkScalar C = pts[0].fX;
3256 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3257 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003258 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003259 if (!SkScalarNearlyEqual(x, xt)) {
3260 continue;
3261 }
3262 tangents->push(SkEvalQuadTangentAt(pts, t));
3263 }
3264}
3265
3266static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3267 SkTDArray<SkVector>* tangents) {
3268 SkScalar y0 = pts[0].fY;
3269 SkScalar y1 = pts[1].fY;
3270 if (!between(y0, y, y1)) {
3271 return;
3272 }
3273 SkScalar x0 = pts[0].fX;
3274 SkScalar x1 = pts[1].fX;
3275 if (!between(x0, x, x1)) {
3276 return;
3277 }
3278 SkScalar dx = x1 - x0;
3279 SkScalar dy = y1 - y0;
3280 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3281 return;
3282 }
3283 SkVector v;
3284 v.set(dx, dy);
3285 tangents->push(v);
3286}
3287
reed@google.com4db592c2013-10-30 17:39:43 +00003288static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3289 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3290}
3291
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003292bool SkPath::contains(SkScalar x, SkScalar y) const {
3293 bool isInverse = this->isInverseFillType();
3294 if (this->isEmpty()) {
3295 return isInverse;
3296 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003297
reed@google.com4db592c2013-10-30 17:39:43 +00003298 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003299 return isInverse;
3300 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003301
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003302 SkPath::Iter iter(*this, true);
3303 bool done = false;
3304 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003305 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003306 do {
3307 SkPoint pts[4];
3308 switch (iter.next(pts, false)) {
3309 case SkPath::kMove_Verb:
3310 case SkPath::kClose_Verb:
3311 break;
3312 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003313 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003314 break;
3315 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003316 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003317 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003318 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003319 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003320 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003321 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003322 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003323 break;
3324 case SkPath::kDone_Verb:
3325 done = true;
3326 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003327 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003328 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003329 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3330 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3331 if (evenOddFill) {
3332 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003333 }
caryclark9aacd902015-12-14 08:38:09 -08003334 if (w) {
3335 return !isInverse;
3336 }
3337 if (onCurveCount <= 1) {
3338 return SkToBool(onCurveCount) ^ isInverse;
3339 }
3340 if ((onCurveCount & 1) || evenOddFill) {
3341 return SkToBool(onCurveCount & 1) ^ isInverse;
3342 }
halcanary9d524f22016-03-29 09:03:52 -07003343 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003344 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3345 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003346 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003347 SkTDArray<SkVector> tangents;
3348 do {
3349 SkPoint pts[4];
3350 int oldCount = tangents.count();
3351 switch (iter.next(pts, false)) {
3352 case SkPath::kMove_Verb:
3353 case SkPath::kClose_Verb:
3354 break;
3355 case SkPath::kLine_Verb:
3356 tangent_line(pts, x, y, &tangents);
3357 break;
3358 case SkPath::kQuad_Verb:
3359 tangent_quad(pts, x, y, &tangents);
3360 break;
3361 case SkPath::kConic_Verb:
3362 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3363 break;
3364 case SkPath::kCubic_Verb:
3365 tangent_cubic(pts, x, y, &tangents);
3366 break;
3367 case SkPath::kDone_Verb:
3368 done = true;
3369 break;
3370 }
3371 if (tangents.count() > oldCount) {
3372 int last = tangents.count() - 1;
3373 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003374 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003375 tangents.remove(last);
3376 } else {
3377 for (int index = 0; index < last; ++index) {
3378 const SkVector& test = tangents[index];
3379 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003380 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3381 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003382 tangents.remove(last);
3383 tangents.removeShuffle(index);
3384 break;
3385 }
3386 }
3387 }
3388 }
3389 } while (!done);
3390 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003391}
fmalitaaa0df4e2015-12-01 09:13:23 -08003392
3393int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3394 SkScalar w, SkPoint pts[], int pow2) {
3395 const SkConic conic(p0, p1, p2, w);
3396 return conic.chopIntoQuadsPOW2(pts, pow2);
3397}
bsalomonedc743a2016-06-01 09:42:31 -07003398
3399bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3400 unsigned* start) {
3401 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3402 return false;
3403 }
3404 SkPath::RawIter iter(path);
3405 SkPoint verbPts[4];
3406 SkPath::Verb v;
3407 SkPoint rectPts[5];
3408 int rectPtCnt = 0;
3409 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3410 switch (v) {
3411 case SkPath::kMove_Verb:
3412 if (0 != rectPtCnt) {
3413 return false;
3414 }
3415 rectPts[0] = verbPts[0];
3416 ++rectPtCnt;
3417 break;
3418 case SkPath::kLine_Verb:
3419 if (5 == rectPtCnt) {
3420 return false;
3421 }
3422 rectPts[rectPtCnt] = verbPts[1];
3423 ++rectPtCnt;
3424 break;
3425 case SkPath::kClose_Verb:
3426 if (4 == rectPtCnt) {
3427 rectPts[4] = rectPts[0];
3428 rectPtCnt = 5;
3429 }
3430 break;
3431 default:
3432 return false;
3433 }
3434 }
3435 if (rectPtCnt < 5) {
3436 return false;
3437 }
3438 if (rectPts[0] != rectPts[4]) {
3439 return false;
3440 }
bsalomon057ae8a2016-07-24 05:37:26 -07003441 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3442 // and pts 1 and 2 the opposite vertical or horizontal edge).
3443 bool vec03IsVertical;
3444 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3445 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3446 // Make sure it has non-zero width and height
3447 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003448 return false;
3449 }
bsalomon057ae8a2016-07-24 05:37:26 -07003450 vec03IsVertical = true;
3451 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3452 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3453 // Make sure it has non-zero width and height
3454 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3455 return false;
3456 }
3457 vec03IsVertical = false;
3458 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003459 return false;
3460 }
bsalomon057ae8a2016-07-24 05:37:26 -07003461 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3462 // set if it is on the bottom edge.
3463 unsigned sortFlags =
3464 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3465 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3466 switch (sortFlags) {
3467 case 0b00:
3468 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3469 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3470 *start = 0;
3471 break;
3472 case 0b01:
3473 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3474 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3475 *start = 1;
3476 break;
3477 case 0b10:
3478 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3479 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3480 *start = 3;
3481 break;
3482 case 0b11:
3483 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3484 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3485 *start = 2;
3486 break;
bsalomonedc743a2016-06-01 09:42:31 -07003487 }
bsalomonedc743a2016-06-01 09:42:31 -07003488 return true;
3489}
bsalomon21af9ca2016-08-25 12:29:23 -07003490
3491void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3492 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3493 SkASSERT(!oval.isEmpty());
3494 SkASSERT(sweepAngle);
3495
3496 path->reset();
3497 path->setIsVolatile(true);
3498 path->setFillType(SkPath::kWinding_FillType);
3499 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3500 path->addOval(oval);
3501 return;
3502 }
3503 if (useCenter) {
3504 path->moveTo(oval.centerX(), oval.centerY());
3505 }
3506 // Arc to mods at 360 and drawArc is not supposed to.
3507 bool forceMoveTo = !useCenter;
3508 while (sweepAngle <= -360.f) {
3509 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3510 startAngle -= 180.f;
3511 path->arcTo(oval, startAngle, -180.f, false);
3512 startAngle -= 180.f;
3513 forceMoveTo = false;
3514 sweepAngle += 360.f;
3515 }
3516 while (sweepAngle >= 360.f) {
3517 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3518 startAngle += 180.f;
3519 path->arcTo(oval, startAngle, 180.f, false);
3520 startAngle += 180.f;
3521 forceMoveTo = false;
3522 sweepAngle -= 360.f;
3523 }
3524 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3525 if (useCenter) {
3526 path->close();
3527 }
3528}
Mike Reed0d7dac82017-02-02 17:45:56 -08003529
3530///////////////////////////////////////////////////////////////////////////////////////////////////
3531#include "SkNx.h"
3532
3533static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3534 SkScalar ts[2];
3535 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3536 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3537 SkASSERT(n >= 0 && n <= 2);
3538 for (int i = 0; i < n; ++i) {
3539 extremas[i] = SkEvalQuadAt(src, ts[i]);
3540 }
3541 extremas[n] = src[2];
3542 return n + 1;
3543}
3544
3545static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3546 SkConic conic(src[0], src[1], src[2], w);
3547 SkScalar ts[2];
3548 int n = conic.findXExtrema(ts);
3549 n += conic.findYExtrema(&ts[n]);
3550 SkASSERT(n >= 0 && n <= 2);
3551 for (int i = 0; i < n; ++i) {
3552 extremas[i] = conic.evalAt(ts[i]);
3553 }
3554 extremas[n] = src[2];
3555 return n + 1;
3556}
3557
3558static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3559 SkScalar ts[4];
3560 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3561 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3562 SkASSERT(n >= 0 && n <= 4);
3563 for (int i = 0; i < n; ++i) {
3564 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3565 }
3566 extremas[n] = src[3];
3567 return n + 1;
3568}
3569
Mike Reed8d3196b2017-02-03 11:34:13 -05003570SkRect SkPath::computeTightBounds() const {
3571 if (0 == this->countVerbs()) {
3572 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003573 }
3574
Mike Reed8d3196b2017-02-03 11:34:13 -05003575 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3576 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003577 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003578
Mike Reed0d7dac82017-02-02 17:45:56 -08003579 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3580 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003581 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003582
3583 // initial with the first MoveTo, so we don't have to check inside the switch
3584 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003585 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003586 for (;;) {
3587 int count = 0;
3588 switch (iter.next(pts)) {
3589 case SkPath::kMove_Verb:
3590 extremas[0] = pts[0];
3591 count = 1;
3592 break;
3593 case SkPath::kLine_Verb:
3594 extremas[0] = pts[1];
3595 count = 1;
3596 break;
3597 case SkPath::kQuad_Verb:
3598 count = compute_quad_extremas(pts, extremas);
3599 break;
3600 case SkPath::kConic_Verb:
3601 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3602 break;
3603 case SkPath::kCubic_Verb:
3604 count = compute_cubic_extremas(pts, extremas);
3605 break;
3606 case SkPath::kClose_Verb:
3607 break;
3608 case SkPath::kDone_Verb:
3609 goto DONE;
3610 }
3611 for (int i = 0; i < count; ++i) {
3612 Sk2s tmp = from_point(extremas[i]);
3613 min = Sk2s::Min(min, tmp);
3614 max = Sk2s::Max(max, tmp);
3615 }
3616 }
3617DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003618 SkRect bounds;
3619 min.store((SkPoint*)&bounds.fLeft);
3620 max.store((SkPoint*)&bounds.fRight);
3621 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003622}
Cary Clarkdf429f32017-11-08 11:44:31 -05003623
3624bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3625 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3626}
3627
3628bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3629 const SkPoint& p3, bool exact) {
3630 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3631 SkPointPriv::EqualsWithinTolerance(p2, p3);
3632}
3633
3634bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3635 const SkPoint& p3, const SkPoint& p4, bool exact) {
3636 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3637 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3638 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3639 SkPointPriv::EqualsWithinTolerance(p3, p4);
3640}