blob: 1b443b2f46b2eda326568f9455d9aa192a76cc65 [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);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000091 if (fEmpty || fHasValidBounds) {
92 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
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635int SkPath::countPoints() const {
636 return fPathRef->countPoints();
637}
638
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000639int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 SkDEBUGCODE(this->validate();)
641
642 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000643 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800645 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647}
648
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000649SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
651 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000652 }
653 return SkPoint::Make(0, 0);
654}
655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656int SkPath::countVerbs() const {
657 return fPathRef->countVerbs();
658}
659
660static inline void copy_verbs_reverse(uint8_t* inorderDst,
661 const uint8_t* reversedSrc,
662 int count) {
663 for (int i = 0; i < count; ++i) {
664 inorderDst[i] = reversedSrc[~i];
665 }
666}
667
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000668int SkPath::getVerbs(uint8_t dst[], int max) const {
669 SkDEBUGCODE(this->validate();)
670
671 SkASSERT(max >= 0);
672 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 int count = SkMin32(max, fPathRef->countVerbs());
674 copy_verbs_reverse(dst, fPathRef->verbs(), count);
675 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676}
677
reed@google.com294dd7b2011-10-11 11:58:32 +0000678bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679 SkDEBUGCODE(this->validate();)
680
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000682 if (count > 0) {
683 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000688 if (lastPt) {
689 lastPt->set(0, 0);
690 }
691 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692}
693
caryclarkaec25102015-04-29 08:28:30 -0700694void SkPath::setPt(int index, SkScalar x, SkScalar y) {
695 SkDEBUGCODE(this->validate();)
696
697 int count = fPathRef->countPoints();
698 if (count <= index) {
699 return;
700 } else {
701 SkPathRef::Editor ed(&fPathRef);
702 ed.atPoint(index)->set(x, y);
703 }
704}
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706void SkPath::setLastPt(SkScalar x, SkScalar y) {
707 SkDEBUGCODE(this->validate();)
708
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000709 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710 if (count == 0) {
711 this->moveTo(x, y);
712 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000713 SkPathRef::Editor ed(&fPathRef);
714 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 }
716}
717
reed@google.com04863fa2011-05-15 04:08:24 +0000718void SkPath::setConvexity(Convexity c) {
719 if (fConvexity != c) {
720 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000721 }
722}
723
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724//////////////////////////////////////////////////////////////////////////////
725// Construction methods
726
reed026beb52015-06-10 14:23:15 -0700727#define DIRTY_AFTER_EDIT \
728 do { \
729 fConvexity = kUnknown_Convexity; \
730 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000731 } while (0)
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733void SkPath::incReserve(U16CPU inc) {
734 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736 SkDEBUGCODE(this->validate();)
737}
738
739void SkPath::moveTo(SkScalar x, SkScalar y) {
740 SkDEBUGCODE(this->validate();)
741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000742 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000744 // remember our index
745 fLastMoveToIndex = fPathRef->countPoints();
746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700748
749 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750}
751
752void SkPath::rMoveTo(SkScalar x, SkScalar y) {
753 SkPoint pt;
754 this->getLastPt(&pt);
755 this->moveTo(pt.fX + x, pt.fY + y);
756}
757
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000758void SkPath::injectMoveToIfNeeded() {
759 if (fLastMoveToIndex < 0) {
760 SkScalar x, y;
761 if (fPathRef->countVerbs() == 0) {
762 x = y = 0;
763 } else {
764 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
765 x = pt.fX;
766 y = pt.fY;
767 }
768 this->moveTo(x, y);
769 }
770}
771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772void SkPath::lineTo(SkScalar x, SkScalar y) {
773 SkDEBUGCODE(this->validate();)
774
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000775 this->injectMoveToIfNeeded();
776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 SkPathRef::Editor ed(&fPathRef);
778 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779
reed@google.comb54455e2011-05-16 14:16:04 +0000780 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781}
782
783void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000784 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 SkPoint pt;
786 this->getLastPt(&pt);
787 this->lineTo(pt.fX + x, pt.fY + y);
788}
789
790void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
791 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000793 this->injectMoveToIfNeeded();
794
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 SkPathRef::Editor ed(&fPathRef);
796 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 pts[0].set(x1, y1);
798 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000799
reed@google.comb54455e2011-05-16 14:16:04 +0000800 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801}
802
803void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000804 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 SkPoint pt;
806 this->getLastPt(&pt);
807 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
808}
809
reed@google.com277c3f82013-05-31 15:17:50 +0000810void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
811 SkScalar w) {
812 // check for <= 0 or NaN with this test
813 if (!(w > 0)) {
814 this->lineTo(x2, y2);
815 } else if (!SkScalarIsFinite(w)) {
816 this->lineTo(x1, y1);
817 this->lineTo(x2, y2);
818 } else if (SK_Scalar1 == w) {
819 this->quadTo(x1, y1, x2, y2);
820 } else {
821 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000822
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000823 this->injectMoveToIfNeeded();
824
reed@google.com277c3f82013-05-31 15:17:50 +0000825 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000826 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000827 pts[0].set(x1, y1);
828 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000829
reed@google.com277c3f82013-05-31 15:17:50 +0000830 DIRTY_AFTER_EDIT;
831 }
832}
833
834void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
835 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000836 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000837 SkPoint pt;
838 this->getLastPt(&pt);
839 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
840}
841
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
843 SkScalar x3, SkScalar y3) {
844 SkDEBUGCODE(this->validate();)
845
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000846 this->injectMoveToIfNeeded();
847
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 SkPathRef::Editor ed(&fPathRef);
849 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 pts[0].set(x1, y1);
851 pts[1].set(x2, y2);
852 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853
reed@google.comb54455e2011-05-16 14:16:04 +0000854 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855}
856
857void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
858 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000859 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkPoint pt;
861 this->getLastPt(&pt);
862 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
863 pt.fX + x3, pt.fY + y3);
864}
865
866void SkPath::close() {
867 SkDEBUGCODE(this->validate();)
868
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000871 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 case kLine_Verb:
873 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000874 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000876 case kMove_Verb: {
877 SkPathRef::Editor ed(&fPathRef);
878 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000880 }
reed@google.com277c3f82013-05-31 15:17:50 +0000881 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000882 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000883 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000884 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000885 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000886 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 }
888 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000889
890 // signal that we need a moveTo to follow us (unless we're done)
891#if 0
892 if (fLastMoveToIndex >= 0) {
893 fLastMoveToIndex = ~fLastMoveToIndex;
894 }
895#else
896 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
897#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898}
899
900///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000901
fmalitac08d53e2015-11-17 09:53:29 -0800902namespace {
903
904template <unsigned N>
905class PointIterator {
906public:
907 PointIterator(SkPath::Direction dir, unsigned startIndex)
908 : fCurrent(startIndex % N)
909 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
910
911 const SkPoint& current() const {
912 SkASSERT(fCurrent < N);
913 return fPts[fCurrent];
914 }
915
916 const SkPoint& next() {
917 fCurrent = (fCurrent + fAdvance) % N;
918 return this->current();
919 }
920
921protected:
922 SkPoint fPts[N];
923
924private:
925 unsigned fCurrent;
926 unsigned fAdvance;
927};
928
929class RectPointIterator : public PointIterator<4> {
930public:
931 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
932 : PointIterator(dir, startIndex) {
933
934 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
935 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
936 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
937 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
938 }
939};
940
941class OvalPointIterator : public PointIterator<4> {
942public:
943 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
944 : PointIterator(dir, startIndex) {
945
946 const SkScalar cx = oval.centerX();
947 const SkScalar cy = oval.centerY();
948
949 fPts[0] = SkPoint::Make(cx, oval.fTop);
950 fPts[1] = SkPoint::Make(oval.fRight, cy);
951 fPts[2] = SkPoint::Make(cx, oval.fBottom);
952 fPts[3] = SkPoint::Make(oval.fLeft, cy);
953 }
954};
955
956class RRectPointIterator : public PointIterator<8> {
957public:
958 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
959 : PointIterator(dir, startIndex) {
960
961 const SkRect& bounds = rrect.getBounds();
962 const SkScalar L = bounds.fLeft;
963 const SkScalar T = bounds.fTop;
964 const SkScalar R = bounds.fRight;
965 const SkScalar B = bounds.fBottom;
966
967 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
968 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
969 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
970 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
971 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
972 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
973 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
974 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
975 }
976};
977
978} // anonymous namespace
979
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000980static void assert_known_direction(int dir) {
981 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
982}
983
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800985 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986}
987
988void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
989 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800990 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
991}
992
993void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000994 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700995 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800996 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000997 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800998 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000999
fmalitac08d53e2015-11-17 09:53:29 -08001000 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001
fmalitac08d53e2015-11-17 09:53:29 -08001002 const int kVerbs = 5; // moveTo + 3x lineTo + close
1003 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004
fmalitac08d53e2015-11-17 09:53:29 -08001005 RectPointIterator iter(rect, dir, startIndex);
1006
1007 this->moveTo(iter.current());
1008 this->lineTo(iter.next());
1009 this->lineTo(iter.next());
1010 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001012
1013 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014}
1015
reed@google.com744faba2012-05-29 19:54:52 +00001016void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1017 SkDEBUGCODE(this->validate();)
1018 if (count <= 0) {
1019 return;
1020 }
1021
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001022 fLastMoveToIndex = fPathRef->countPoints();
1023
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001024 // +close makes room for the extra kClose_Verb
1025 SkPathRef::Editor ed(&fPathRef, count+close, count);
1026
1027 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001028 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001029 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1030 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001031 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032
reed@google.com744faba2012-05-29 19:54:52 +00001033 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001034 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001035 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001036 }
1037
reed@google.com744faba2012-05-29 19:54:52 +00001038 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001039 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001040}
1041
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042#include "SkGeometry.h"
1043
reedf90ea012015-01-29 12:03:58 -08001044static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1045 SkPoint* pt) {
1046 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001047 // Chrome uses this path to move into and out of ovals. If not
1048 // treated as a special case the moves can distort the oval's
1049 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001050 pt->set(oval.fRight, oval.centerY());
1051 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052 } else if (0 == oval.width() && 0 == oval.height()) {
1053 // Chrome will sometimes create 0 radius round rects. Having degenerate
1054 // quad segments in the path prevents the path from being recognized as
1055 // a rect.
1056 // TODO: optimizing the case where only one of width or height is zero
1057 // should also be considered. This case, however, doesn't seem to be
1058 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001059 pt->set(oval.fRight, oval.fTop);
1060 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 }
reedf90ea012015-01-29 12:03:58 -08001062 return false;
1063}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
reedd5d27d92015-02-09 13:54:43 -08001065// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1066//
1067static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1068 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1069 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1070 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071
1072 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001073 loss in radians-conversion and/or sin/cos, we may end up with coincident
1074 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1075 of drawing a nearly complete circle (good).
1076 e.g. canvas.drawArc(0, 359.99, ...)
1077 -vs- canvas.drawArc(0, 359.9, ...)
1078 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079 */
reedd5d27d92015-02-09 13:54:43 -08001080 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081 SkScalar sw = SkScalarAbs(sweepAngle);
1082 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1083 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1084 // make a guess at a tiny angle (in radians) to tweak by
1085 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1086 // not sure how much will be enough, so we use a loop
1087 do {
1088 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001089 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1090 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091 }
1092 }
reedd5d27d92015-02-09 13:54:43 -08001093 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1094}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001095
reed9e779d42015-02-17 11:43:14 -08001096/**
1097 * If this returns 0, then the caller should just line-to the singlePt, else it should
1098 * ignore singlePt and append the specified number of conics.
1099 */
reedd5d27d92015-02-09 13:54:43 -08001100static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001101 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1102 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001103 SkMatrix matrix;
1104
1105 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1106 matrix.postTranslate(oval.centerX(), oval.centerY());
1107
reed9e779d42015-02-17 11:43:14 -08001108 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1109 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001110 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001111 }
1112 return count;
reedd5d27d92015-02-09 13:54:43 -08001113}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001114
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001117 SkRRect rrect;
1118 rrect.setRectRadii(rect, (const SkVector*) radii);
1119 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120}
1121
reed@google.com4ed0fb72012-12-12 20:48:18 +00001122void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001123 // legacy start indices: 6 (CW) and 7(CCW)
1124 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1125}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001126
fmalitac08d53e2015-11-17 09:53:29 -08001127void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1128 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129
fmalitac08d53e2015-11-17 09:53:29 -08001130 if (rrect.isEmpty()) {
1131 return;
reed1b28a3a2014-12-17 14:42:09 -08001132 }
fmalitac08d53e2015-11-17 09:53:29 -08001133
caryclarkda707bf2015-11-19 14:47:43 -08001134 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001135 const SkRect& bounds = rrect.getBounds();
1136
1137 if (rrect.isRect()) {
1138 // degenerate(rect) => radii points are collapsing
1139 this->addRect(bounds, dir, (startIndex + 1) / 2);
1140 } else if (rrect.isOval()) {
1141 // degenerate(oval) => line points are collapsing
1142 this->addOval(bounds, dir, startIndex / 2);
1143 } else {
1144 fFirstDirection = this->hasOnlyMoveTos() ?
1145 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1146
1147 SkAutoPathBoundsUpdate apbu(this, bounds);
1148 SkAutoDisableDirectionCheck addc(this);
1149
1150 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1151 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1152 const SkScalar weight = SK_ScalarRoot2Over2;
1153
1154 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1155 const int kVerbs = startsWithConic
1156 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1157 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1158 this->incReserve(kVerbs);
1159
1160 RRectPointIterator rrectIter(rrect, dir, startIndex);
1161 // Corner iterator indices follow the collapsed radii model,
1162 // adjusted such that the start pt is "behind" the radii start pt.
1163 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1164 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1165
1166 this->moveTo(rrectIter.current());
1167 if (startsWithConic) {
1168 for (unsigned i = 0; i < 3; ++i) {
1169 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1170 this->lineTo(rrectIter.next());
1171 }
1172 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1173 // final lineTo handled by close().
1174 } else {
1175 for (unsigned i = 0; i < 4; ++i) {
1176 this->lineTo(rrectIter.next());
1177 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1178 }
1179 }
1180 this->close();
1181
caryclarkda707bf2015-11-19 14:47:43 -08001182 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001183 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001184
fmalitac08d53e2015-11-17 09:53:29 -08001185 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1186 }
1187
1188 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001189}
1190
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001192 int count = fPathRef->countVerbs();
1193 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1194 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001195 if (*verbs == kLine_Verb ||
1196 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001197 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001198 *verbs == kCubic_Verb) {
1199 return false;
1200 }
1201 ++verbs;
1202 }
1203 return true;
1204}
1205
Brian Osmana2318572017-07-10 15:09:26 -04001206bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1207 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001208 if (count < 2) {
1209 return true;
1210 }
Brian Osmana2318572017-07-10 15:09:26 -04001211 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001212 const SkPoint& first = *pts;
1213 for (int index = 1; index < count; ++index) {
1214 if (first != pts[index]) {
1215 return false;
1216 }
1217 }
1218 return true;
1219}
1220
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001221void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1222 Direction dir) {
1223 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001224
humper@google.com75e3ca12013-04-08 21:44:11 +00001225 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001226 return;
1227 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001228
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001229 SkRRect rrect;
1230 rrect.setRectXY(rect, rx, ry);
1231 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001232}
1233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001235 // legacy start index: 1
1236 this->addOval(oval, dir, 1);
1237}
1238
1239void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001240 assert_known_direction(dir);
1241
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001242 /* If addOval() is called after previous moveTo(),
1243 this path is still marked as an oval. This is used to
1244 fit into WebKit's calling sequences.
1245 We can't simply check isEmpty() in this case, as additional
1246 moveTo() would mark the path non empty.
1247 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001248 bool isOval = hasOnlyMoveTos();
1249 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001250 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001251 } else {
reed026beb52015-06-10 14:23:15 -07001252 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001253 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001254
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001255 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 SkAutoPathBoundsUpdate apbu(this, oval);
1257
fmalitac08d53e2015-11-17 09:53:29 -08001258 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1259 const int kVerbs = 6; // moveTo + 4x conicTo + close
1260 this->incReserve(kVerbs);
1261
1262 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1263 // The corner iterator pts are tracking "behind" the oval/radii pts.
1264 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001265 const SkScalar weight = SK_ScalarRoot2Over2;
1266
fmalitac08d53e2015-11-17 09:53:29 -08001267 this->moveTo(ovalIter.current());
1268 for (unsigned i = 0; i < 4; ++i) {
1269 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001270 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272
fmalitac08d53e2015-11-17 09:53:29 -08001273 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1274
robertphillips@google.com466310d2013-12-03 16:43:54 +00001275 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001276
bsalomon78d58d12016-05-27 09:17:04 -07001277 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001278}
1279
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1281 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001282 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 }
1284}
1285
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1287 bool forceMoveTo) {
1288 if (oval.width() < 0 || oval.height() < 0) {
1289 return;
1290 }
1291
reedf90ea012015-01-29 12:03:58 -08001292 if (fPathRef->countVerbs() == 0) {
1293 forceMoveTo = true;
1294 }
1295
1296 SkPoint lonePt;
1297 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1298 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1299 return;
1300 }
1301
reedd5d27d92015-02-09 13:54:43 -08001302 SkVector startV, stopV;
1303 SkRotationDirection dir;
1304 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1305
reed9e779d42015-02-17 11:43:14 -08001306 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001307
1308 // At this point, we know that the arc is not a lone point, but startV == stopV
1309 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1310 // cannot handle it.
1311 if (startV == stopV) {
1312 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1313 SkScalar radiusX = oval.width() / 2;
1314 SkScalar radiusY = oval.height() / 2;
1315 // We cannot use SkScalarSinCos function in the next line because
1316 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1317 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001318 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001319 // make sin(endAngle) to be 0 which will then draw a dot.
1320 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1321 oval.centerY() + radiusY * sk_float_sin(endAngle));
1322 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1323 return;
1324 }
1325
reedd5d27d92015-02-09 13:54:43 -08001326 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001327 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001328 if (count) {
1329 this->incReserve(count * 2 + 1);
1330 const SkPoint& pt = conics[0].fPts[0];
1331 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1332 for (int i = 0; i < count; ++i) {
1333 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1334 }
reed9e779d42015-02-17 11:43:14 -08001335 } else {
1336 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001337 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338}
1339
caryclark55d49052016-01-23 05:07:04 -08001340// This converts the SVG arc to conics.
1341// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1342// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1343// See also SVG implementation notes:
1344// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1345// Note that arcSweep bool value is flipped from the original implementation.
1346void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1347 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001348 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001349 SkPoint srcPts[2];
1350 this->getLastPt(&srcPts[0]);
1351 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1352 // joining the endpoints.
1353 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1354 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001355 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001356 return;
1357 }
1358 // If the current point and target point for the arc are identical, it should be treated as a
1359 // zero length path. This ensures continuity in animations.
1360 srcPts[1].set(x, y);
1361 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001362 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001363 return;
1364 }
1365 rx = SkScalarAbs(rx);
1366 ry = SkScalarAbs(ry);
1367 SkVector midPointDistance = srcPts[0] - srcPts[1];
1368 midPointDistance *= 0.5f;
1369
1370 SkMatrix pointTransform;
1371 pointTransform.setRotate(-angle);
1372
1373 SkPoint transformedMidPoint;
1374 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1375 SkScalar squareRx = rx * rx;
1376 SkScalar squareRy = ry * ry;
1377 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1378 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1379
1380 // Check if the radii are big enough to draw the arc, scale radii if not.
1381 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1382 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1383 if (radiiScale > 1) {
1384 radiiScale = SkScalarSqrt(radiiScale);
1385 rx *= radiiScale;
1386 ry *= radiiScale;
1387 }
1388
1389 pointTransform.setScale(1 / rx, 1 / ry);
1390 pointTransform.preRotate(-angle);
1391
1392 SkPoint unitPts[2];
1393 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1394 SkVector delta = unitPts[1] - unitPts[0];
1395
1396 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1397 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1398
1399 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1400 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1401 scaleFactor = -scaleFactor;
1402 }
1403 delta.scale(scaleFactor);
1404 SkPoint centerPoint = unitPts[0] + unitPts[1];
1405 centerPoint *= 0.5f;
1406 centerPoint.offset(-delta.fY, delta.fX);
1407 unitPts[0] -= centerPoint;
1408 unitPts[1] -= centerPoint;
1409 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1410 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1411 SkScalar thetaArc = theta2 - theta1;
1412 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1413 thetaArc += SK_ScalarPI * 2;
1414 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1415 thetaArc -= SK_ScalarPI * 2;
1416 }
1417 pointTransform.setRotate(angle);
1418 pointTransform.preScale(rx, ry);
1419
1420 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1421 SkScalar thetaWidth = thetaArc / segments;
1422 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1423 if (!SkScalarIsFinite(t)) {
1424 return;
1425 }
1426 SkScalar startTheta = theta1;
1427 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1428 for (int i = 0; i < segments; ++i) {
1429 SkScalar endTheta = startTheta + thetaWidth;
1430 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1431
1432 unitPts[1].set(cosEndTheta, sinEndTheta);
1433 unitPts[1] += centerPoint;
1434 unitPts[0] = unitPts[1];
1435 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1436 SkPoint mapped[2];
1437 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1438 this->conicTo(mapped[0], mapped[1], w);
1439 startTheta = endTheta;
1440 }
1441}
1442
1443void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1444 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1445 SkPoint currentPoint;
1446 this->getLastPt(&currentPoint);
1447 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1448}
1449
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001450void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 if (oval.isEmpty() || 0 == sweepAngle) {
1452 return;
1453 }
1454
1455 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1456
1457 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001458 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1459 // See SkPath::addOval() docs.
1460 SkScalar startOver90 = startAngle / 90.f;
1461 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1462 SkScalar error = startOver90 - startOver90I;
1463 if (SkScalarNearlyEqual(error, 0)) {
1464 // Index 1 is at startAngle == 0.
1465 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1466 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1467 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1468 (unsigned) startIndex);
1469 return;
1470 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 }
bsalomon1978ce22016-05-31 10:42:16 -07001472 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473}
1474
1475/*
1476 Need to handle the case when the angle is sharp, and our computed end-points
1477 for the arc go behind pt1 and/or p2...
1478*/
reedc7789042015-01-29 12:59:11 -08001479void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001480 if (radius == 0) {
1481 this->lineTo(x1, y1);
1482 return;
1483 }
1484
1485 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001486
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 // need to know our prev pt so we can construct tangent vectors
1488 {
1489 SkPoint start;
1490 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001491 // Handle degenerate cases by adding a line to the first point and
1492 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 before.setNormalize(x1 - start.fX, y1 - start.fY);
1494 after.setNormalize(x2 - x1, y2 - y1);
1495 }
reed@google.comabf15c12011-01-18 20:35:51 +00001496
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 SkScalar cosh = SkPoint::DotProduct(before, after);
1498 SkScalar sinh = SkPoint::CrossProduct(before, after);
1499
1500 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001501 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502 return;
1503 }
reed@google.comabf15c12011-01-18 20:35:51 +00001504
Mike Reeda99b6ce2017-02-04 11:04:26 -05001505 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506
Mike Reeda99b6ce2017-02-04 11:04:26 -05001507 SkScalar xx = x1 - dist * before.fX;
1508 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001509 after.setLength(dist);
1510 this->lineTo(xx, yy);
1511 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1512 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513}
1514
1515///////////////////////////////////////////////////////////////////////////////
1516
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001517void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 SkMatrix matrix;
1519
1520 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001521 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522}
1523
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001524void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001527 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 SkPoint pts[4];
1529 Verb verb;
1530
Cary Clark9480d822017-10-19 18:01:13 -04001531 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001532 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 while ((verb = iter.next(pts)) != kDone_Verb) {
1534 switch (verb) {
1535 case kMove_Verb:
1536 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001537 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1538 injectMoveToIfNeeded(); // In case last contour is closed
1539 this->lineTo(pts[0]);
1540 } else {
1541 this->moveTo(pts[0]);
1542 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 break;
1544 case kLine_Verb:
1545 proc(matrix, &pts[1], &pts[1], 1);
1546 this->lineTo(pts[1]);
1547 break;
1548 case kQuad_Verb:
1549 proc(matrix, &pts[1], &pts[1], 2);
1550 this->quadTo(pts[1], pts[2]);
1551 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001552 case kConic_Verb:
1553 proc(matrix, &pts[1], &pts[1], 2);
1554 this->conicTo(pts[1], pts[2], iter.conicWeight());
1555 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 case kCubic_Verb:
1557 proc(matrix, &pts[1], &pts[1], 3);
1558 this->cubicTo(pts[1], pts[2], pts[3]);
1559 break;
1560 case kClose_Verb:
1561 this->close();
1562 break;
1563 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001564 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001566 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
1568}
1569
1570///////////////////////////////////////////////////////////////////////////////
1571
reed@google.com277c3f82013-05-31 15:17:50 +00001572static int pts_in_verb(unsigned verb) {
1573 static const uint8_t gPtsInVerb[] = {
1574 1, // kMove
1575 1, // kLine
1576 2, // kQuad
1577 2, // kConic
1578 3, // kCubic
1579 0, // kClose
1580 0 // kDone
1581 };
1582
1583 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1584 return gPtsInVerb[verb];
1585}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587// ignore the last point of the 1st contour
1588void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001589 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1590 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591 return;
1592 }
caryclark51c56782016-11-07 05:09:28 -08001593 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1594 SkASSERT(verbsEnd[0] == kMove_Verb);
1595 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1596 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597
caryclark51c56782016-11-07 05:09:28 -08001598 while (verbs < verbsEnd) {
1599 uint8_t v = *verbs++;
1600 pts -= pts_in_verb(v);
1601 switch (v) {
1602 case kMove_Verb:
1603 // if the path has multiple contours, stop after reversing the last
1604 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001606 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 break;
1608 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001609 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001611 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001612 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001613 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001615 this->cubicTo(pts[2], pts[1], pts[0]);
1616 break;
1617 case kClose_Verb:
1618 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 break;
1620 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001621 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622 break;
1623 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 }
1625}
1626
reed@google.com63d73742012-01-10 15:33:12 +00001627void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001628 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001629
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001630 const SkPoint* pts = src.fPathRef->pointsEnd();
1631 // we will iterator through src's verbs backwards
1632 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1633 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001634 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001635
1636 bool needMove = true;
1637 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001638 while (verbs < verbsEnd) {
1639 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001640 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001641
1642 if (needMove) {
1643 --pts;
1644 this->moveTo(pts->fX, pts->fY);
1645 needMove = false;
1646 }
1647 pts -= n;
1648 switch (v) {
1649 case kMove_Verb:
1650 if (needClose) {
1651 this->close();
1652 needClose = false;
1653 }
1654 needMove = true;
1655 pts += 1; // so we see the point in "if (needMove)" above
1656 break;
1657 case kLine_Verb:
1658 this->lineTo(pts[0]);
1659 break;
1660 case kQuad_Verb:
1661 this->quadTo(pts[1], pts[0]);
1662 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001663 case kConic_Verb:
1664 this->conicTo(pts[1], pts[0], *--conicWeights);
1665 break;
reed@google.com63d73742012-01-10 15:33:12 +00001666 case kCubic_Verb:
1667 this->cubicTo(pts[2], pts[1], pts[0]);
1668 break;
1669 case kClose_Verb:
1670 needClose = true;
1671 break;
1672 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001673 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001674 }
1675 }
1676}
1677
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678///////////////////////////////////////////////////////////////////////////////
1679
1680void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1681 SkMatrix matrix;
1682
1683 matrix.setTranslate(dx, dy);
1684 this->transform(matrix, dst);
1685}
1686
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1688 int level = 2) {
1689 if (--level >= 0) {
1690 SkPoint tmp[7];
1691
1692 SkChopCubicAtHalf(pts, tmp);
1693 subdivide_cubic_to(path, &tmp[0], level);
1694 subdivide_cubic_to(path, &tmp[3], level);
1695 } else {
1696 path->cubicTo(pts[1], pts[2], pts[3]);
1697 }
1698}
1699
1700void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1701 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001702 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 dst = (SkPath*)this;
1704 }
1705
tomhudson@google.com8d430182011-06-06 19:11:19 +00001706 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 SkPath tmp;
1708 tmp.fFillType = fFillType;
1709
1710 SkPath::Iter iter(*this, false);
1711 SkPoint pts[4];
1712 SkPath::Verb verb;
1713
reed@google.com4a3b7142012-05-16 17:16:46 +00001714 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 switch (verb) {
1716 case kMove_Verb:
1717 tmp.moveTo(pts[0]);
1718 break;
1719 case kLine_Verb:
1720 tmp.lineTo(pts[1]);
1721 break;
1722 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001723 // promote the quad to a conic
1724 tmp.conicTo(pts[1], pts[2],
1725 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001727 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001728 tmp.conicTo(pts[1], pts[2],
1729 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001730 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 case kCubic_Verb:
1732 subdivide_cubic_to(&tmp, pts);
1733 break;
1734 case kClose_Verb:
1735 tmp.close();
1736 break;
1737 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001738 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 break;
1740 }
1741 }
1742
1743 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001744 SkPathRef::Editor ed(&dst->fPathRef);
1745 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001746 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001748 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1749
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001752 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001753 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001755
reed026beb52015-06-10 14:23:15 -07001756 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1757 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001758 } else {
1759 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001760 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1761 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001762 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001763 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1764 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001765 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001766 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001767 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001768 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001769 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001770 }
1771 }
1772
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 SkDEBUGCODE(dst->validate();)
1774 }
1775}
1776
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777///////////////////////////////////////////////////////////////////////////////
1778///////////////////////////////////////////////////////////////////////////////
1779
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001780enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001781 kEmptyContour_SegmentState, // The current contour is empty. We may be
1782 // starting processing or we may have just
1783 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001784 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1785 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1786 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787};
1788
1789SkPath::Iter::Iter() {
1790#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001791 fPts = nullptr;
1792 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001795 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796#endif
1797 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001798 fVerbs = nullptr;
1799 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 fNeedClose = false;
1801}
1802
1803SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1804 this->setPath(path, forceClose);
1805}
1806
1807void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 fPts = path.fPathRef->points();
1809 fVerbs = path.fPathRef->verbs();
1810 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001811 fConicWeights = path.fPathRef->conicWeights();
1812 if (fConicWeights) {
1813 fConicWeights -= 1; // begin one behind
1814 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001816 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 fForceClose = SkToU8(forceClose);
1818 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001819 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820}
1821
1822bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001823 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 return false;
1825 }
1826 if (fForceClose) {
1827 return true;
1828 }
1829
1830 const uint8_t* verbs = fVerbs;
1831 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001832
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001833 if (kMove_Verb == *(verbs - 1)) {
1834 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 }
1836
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 while (verbs > stop) {
1838 // verbs points one beyond the current verb, decrement first.
1839 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 if (kMove_Verb == v) {
1841 break;
1842 }
1843 if (kClose_Verb == v) {
1844 return true;
1845 }
1846 }
1847 return false;
1848}
1849
1850SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001851 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001853 // A special case: if both points are NaN, SkPoint::operation== returns
1854 // false, but the iterator expects that they are treated as the same.
1855 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001856 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1857 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001858 return kClose_Verb;
1859 }
1860
reed@google.com9e25dbf2012-05-15 17:05:38 +00001861 pts[0] = fLastPt;
1862 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 fLastPt = fMoveTo;
1864 fCloseLine = true;
1865 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001866 } else {
1867 pts[0] = fMoveTo;
1868 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870}
1871
reed@google.com9e25dbf2012-05-15 17:05:38 +00001872const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 if (fSegmentState == kAfterMove_SegmentState) {
1874 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001876 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1879 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001880 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001881 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882}
1883
caryclarke8c56662015-07-14 11:19:26 -07001884void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 // We need to step over anything that will not move the current draw point
1886 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001887 const uint8_t* lastMoveVerb = nullptr;
1888 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001889 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 SkPoint lastPt = fLastPt;
1891 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001892 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 switch (verb) {
1894 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 // Keep a record of this most recent move
1896 lastMoveVerb = fVerbs;
1897 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001898 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001899 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001900 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 fPts++;
1902 break;
1903
1904 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001905 // A close when we are in a segment is always valid except when it
1906 // follows a move which follows a segment.
1907 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 return;
1909 }
1910 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001911 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 break;
1913
1914 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001915 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 if (lastMoveVerb) {
1917 fVerbs = lastMoveVerb;
1918 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001919 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 return;
1921 }
1922 return;
1923 }
1924 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 fPts++;
1927 break;
1928
reed@google.com277c3f82013-05-31 15:17:50 +00001929 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001931 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 if (lastMoveVerb) {
1933 fVerbs = lastMoveVerb;
1934 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001935 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 return;
1937 }
1938 return;
1939 }
1940 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001941 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001943 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 break;
1945
1946 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001947 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 if (lastMoveVerb) {
1949 fVerbs = lastMoveVerb;
1950 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001951 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 return;
1953 }
1954 return;
1955 }
1956 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001957 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 fPts += 3;
1959 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001960
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001962 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 }
1964 }
1965}
1966
reed@google.com4a3b7142012-05-16 17:16:46 +00001967SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 // Close the curve if requested and if there is some curve to close
1972 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001973 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 return kLine_Verb;
1975 }
1976 fNeedClose = false;
1977 return kClose_Verb;
1978 }
1979 return kDone_Verb;
1980 }
1981
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001982 // fVerbs is one beyond the current verb, decrement first
1983 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 const SkPoint* SK_RESTRICT srcPts = fPts;
1985 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986
1987 switch (verb) {
1988 case kMove_Verb:
1989 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001990 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991 verb = this->autoClose(pts);
1992 if (verb == kClose_Verb) {
1993 fNeedClose = false;
1994 }
1995 return (Verb)verb;
1996 }
1997 if (fVerbs == fVerbStop) { // might be a trailing moveto
1998 return kDone_Verb;
1999 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002000 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002001 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002003 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002004 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 fNeedClose = fForceClose;
2006 break;
2007 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002008 pts[0] = this->cons_moveTo();
2009 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 fLastPt = srcPts[0];
2011 fCloseLine = false;
2012 srcPts += 1;
2013 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002014 case kConic_Verb:
2015 fConicWeights += 1;
2016 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002018 pts[0] = this->cons_moveTo();
2019 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 fLastPt = srcPts[1];
2021 srcPts += 2;
2022 break;
2023 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002024 pts[0] = this->cons_moveTo();
2025 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 fLastPt = srcPts[2];
2027 srcPts += 3;
2028 break;
2029 case kClose_Verb:
2030 verb = this->autoClose(pts);
2031 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002032 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033 } else {
2034 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002035 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002037 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038 break;
2039 }
2040 fPts = srcPts;
2041 return (Verb)verb;
2042}
2043
2044///////////////////////////////////////////////////////////////////////////////
2045
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002047 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048*/
2049
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002050size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2051 SkRect oval;
2052 SkRRect rrect;
2053 bool isCCW;
2054 unsigned start;
2055 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2056 rrect.setOval(oval);
2057 // Convert to rrect start indices.
2058 start *= 2;
2059 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2060 return false;
2061 }
2062 if (!storage) {
2063 // packed header, rrect, start index.
2064 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002065 }
2066
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002067 SkWBuffer buffer(storage);
2068 // Rewrite header's first direction based on rrect direction.
2069 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2070 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2071 packedHeader |= firstDir << kDirection_SerializationShift;
2072 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2073 buffer.write32(packedHeader);
2074 rrect.writeToBuffer(&buffer);
2075 buffer.write32(SkToS32(start));
2076 buffer.padToAlign4();
2077 return buffer.pos();
2078}
2079
2080size_t SkPath::writeToMemory(void* storage) const {
2081 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082
robertphillips@google.com466310d2013-12-03 16:43:54 +00002083 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002084 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002085 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002086 (fIsVolatile << kIsVolatile_SerializationShift) |
2087 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002088 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2089 return bytes;
2090 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002091
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002092 SkWBuffer buffer(storage);
2093
2094 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2095 if (nullptr == storage) {
2096 // packed header, pathref, start index
2097 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2098 return SkAlign4(byteCount);
2099 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002100 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002101 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002102
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002103 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002104
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002105 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002106 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002107}
2108
Mike Reed41a930f2017-07-26 17:33:44 -04002109sk_sp<SkData> SkPath::serialize() const {
2110 size_t size = this->writeToMemory(nullptr);
2111 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2112 this->writeToMemory(data->writable_data());
2113 return data;
2114}
2115
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002116size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002117 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002118
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002119 int32_t packed;
2120 if (!buffer.readS32(&packed)) {
2121 return 0;
2122 }
2123
reed8f086022015-06-11 14:22:19 -07002124 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002125 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2126 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2127 if (version >= kPathPrivTypeEnumVersion) {
2128 SerializationType type =
2129 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2130 switch (type) {
2131 case SerializationType::kRRect: {
2132 Direction rrectDir;
2133 SkRRect rrect;
2134 int32_t start;
2135 switch (dir) {
2136 case SkPathPriv::kCW_FirstDirection:
2137 rrectDir = kCW_Direction;
2138 break;
2139 case SkPathPriv::kCCW_FirstDirection:
2140 rrectDir = kCCW_Direction;
2141 break;
2142 default:
2143 return 0;
2144 }
2145 if (!rrect.readFromBuffer(&buffer)) {
2146 return 0;
2147 }
2148 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2149 return 0;
2150 }
2151 this->reset();
2152 this->addRRect(rrect, rrectDir, SkToUInt(start));
2153 this->setFillType(fillType);
2154 buffer.skipToAlign4();
2155 return buffer.pos();
2156 }
2157 case SerializationType::kGeneral:
2158 // Fall through to general path deserialization
2159 break;
2160 default:
2161 return 0;
2162 }
2163 }
caryclark69006412016-02-17 12:16:27 -08002164 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2165 return 0;
2166 }
mtklein1b249332015-07-07 12:21:21 -07002167
Mike Kleinb9b5de52017-09-27 13:26:22 -04002168 fConvexity.store( (Convexity)((packed >> kConvexity_SerializationShift) & 0xFF) );
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002169 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002170 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002171 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002172 if (!pathRef) {
2173 return 0;
2174 }
2175
2176 fPathRef.reset(pathRef);
2177 SkDEBUGCODE(this->validate();)
2178 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002179
reed8f086022015-06-11 14:22:19 -07002180 // compatibility check
2181 if (version < kPathPrivFirstDirection_Version) {
2182 switch (dir) { // old values
2183 case 0:
2184 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2185 break;
2186 case 1:
2187 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2188 break;
2189 case 2:
2190 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2191 break;
2192 default:
Adrienne Walkerffbdd022017-09-22 10:43:26 -07002193 return 0;
reed8f086022015-06-11 14:22:19 -07002194 }
2195 } else {
2196 fFirstDirection = dir;
2197 }
2198
ajumaf8aec582016-01-13 13:46:31 -08002199 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200}
2201
2202///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002203
Ben Wagner4d1955c2017-03-10 13:08:15 -05002204#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002205#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002206#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002207
reed@google.com51bbe752013-01-17 22:07:50 +00002208static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002209 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002210 str->append(label);
2211 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002212
reed@google.com51bbe752013-01-17 22:07:50 +00002213 const SkScalar* values = &pts[0].fX;
2214 count *= 2;
2215
2216 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002217 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002218 if (i < count - 1) {
2219 str->append(", ");
2220 }
2221 }
reed@google.com277c3f82013-05-31 15:17:50 +00002222 if (conicWeight >= 0) {
2223 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002224 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002225 }
caryclark08fa28c2014-10-23 13:08:56 -07002226 str->append(");");
reede05fed02014-12-15 07:59:53 -08002227 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002228 str->append(" // ");
2229 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002230 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002231 if (i < count - 1) {
2232 str->append(", ");
2233 }
2234 }
2235 if (conicWeight >= 0) {
2236 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002237 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002238 }
2239 }
2240 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002241}
2242
caryclarke9562592014-09-15 09:26:09 -07002243void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002244 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002245 Iter iter(*this, forceClose);
2246 SkPoint pts[4];
2247 Verb verb;
2248
reed@google.com51bbe752013-01-17 22:07:50 +00002249 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002250 char const * const gFillTypeStrs[] = {
2251 "Winding",
2252 "EvenOdd",
2253 "InverseWinding",
2254 "InverseEvenOdd",
2255 };
2256 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2257 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002258 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002259 switch (verb) {
2260 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002261 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002262 break;
2263 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002264 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002265 break;
2266 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002267 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002268 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002269 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002270 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002271 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002272 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002273 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002274 break;
2275 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002276 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002277 break;
2278 default:
2279 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2280 verb = kDone_Verb; // stop the loop
2281 break;
2282 }
caryclark1049f122015-04-20 08:31:59 -07002283 if (!wStream && builder.size()) {
2284 SkDebugf("%s", builder.c_str());
2285 builder.reset();
2286 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002287 }
caryclark66a5d8b2014-06-24 08:30:15 -07002288 if (wStream) {
2289 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002290 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002291}
2292
reed@android.come522ca52009-11-23 20:10:41 +00002293void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002294 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002295}
2296
2297void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002298 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002299}
2300
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002301
Cary Clark0461e9f2017-08-25 15:13:38 -04002302bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002303 if ((fFillType & ~3) != 0) {
2304 return false;
2305 }
reed@google.comabf15c12011-01-18 20:35:51 +00002306
djsollen@google.com077348c2012-10-22 20:23:32 +00002307#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002308 if (!fBoundsIsDirty) {
2309 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002310
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002311 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002312 if (SkToBool(fIsFinite) != isFinite) {
2313 return false;
2314 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002315
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002316 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002317 // if we're empty, fBounds may be empty but translated, so we can't
2318 // necessarily compare to bounds directly
2319 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2320 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002321 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2322 return false;
2323 }
reed@android.come522ca52009-11-23 20:10:41 +00002324 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002325 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002326 if (!fBounds.isEmpty()) {
2327 return false;
2328 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002329 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002330 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002331 if (!fBounds.contains(bounds)) {
2332 return false;
2333 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002334 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002335 }
reed@android.come522ca52009-11-23 20:10:41 +00002336 }
2337 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002338#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002339 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002340}
reed@android.come522ca52009-11-23 20:10:41 +00002341
reed@google.com04863fa2011-05-15 04:08:24 +00002342///////////////////////////////////////////////////////////////////////////////
2343
reed@google.com0b7b9822011-05-16 12:29:27 +00002344static int sign(SkScalar x) { return x < 0; }
2345#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002346
robertphillipsc506e302014-09-16 09:43:31 -07002347enum DirChange {
2348 kLeft_DirChange,
2349 kRight_DirChange,
2350 kStraight_DirChange,
2351 kBackwards_DirChange,
2352
2353 kInvalid_DirChange
2354};
2355
2356
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002357static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002358 // The error epsilon was empirically derived; worse case round rects
2359 // with a mid point outset by 2x float epsilon in tests had an error
2360 // of 12.
2361 const int epsilon = 16;
2362 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2363 return false;
2364 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002365 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002366 int aBits = SkFloatAs2sCompliment(compA);
2367 int bBits = SkFloatAs2sCompliment(compB);
2368 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002369}
2370
caryclarkb4216502015-03-02 13:02:34 -08002371static bool approximately_zero_when_compared_to(double x, double y) {
2372 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002373}
2374
caryclarkb4216502015-03-02 13:02:34 -08002375
reed@google.com04863fa2011-05-15 04:08:24 +00002376// only valid for a single contour
2377struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002378 Convexicator()
2379 : fPtCount(0)
2380 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002381 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002382 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002383 , fIsCurve(false)
2384 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002385 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002386 // warnings
djsollen2f124632016-04-29 13:53:05 -07002387 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002388 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002389 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002390 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002391 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002392
2393 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002394 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002395 }
2396
2397 SkPath::Convexity getConvexity() const { return fConvexity; }
2398
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002399 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002400 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002401
reed@google.com04863fa2011-05-15 04:08:24 +00002402 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002403 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002404 return;
2405 }
2406
2407 if (0 == fPtCount) {
2408 fCurrPt = pt;
2409 ++fPtCount;
2410 } else {
2411 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002412 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002413 if (!SkScalarIsFinite(lengthSqd)) {
2414 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002415 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002416 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002417 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 fCurrPt = pt;
2419 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002420 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 } else {
2422 SkASSERT(fPtCount > 2);
2423 this->addVec(vec);
2424 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002425
reed@google.com85b6e392011-05-15 20:25:17 +00002426 int sx = sign(vec.fX);
2427 int sy = sign(vec.fY);
2428 fDx += (sx != fSx);
2429 fDy += (sy != fSy);
2430 fSx = sx;
2431 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002432
reed@google.com85b6e392011-05-15 20:25:17 +00002433 if (fDx > 3 || fDy > 3) {
2434 fConvexity = SkPath::kConcave_Convexity;
2435 }
reed@google.com04863fa2011-05-15 04:08:24 +00002436 }
2437 }
2438 }
2439
2440 void close() {
2441 if (fPtCount > 2) {
2442 this->addVec(fFirstVec);
2443 }
2444 }
2445
caryclarkb4216502015-03-02 13:02:34 -08002446 DirChange directionChange(const SkVector& curVec) {
2447 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2448
2449 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2450 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2451 largest = SkTMax(largest, -smallest);
2452
2453 if (!almost_equal(largest, largest + cross)) {
2454 int sign = SkScalarSignAsInt(cross);
2455 if (sign) {
2456 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2457 }
2458 }
2459
2460 if (cross) {
2461 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2462 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2463 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2464 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2465 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2466 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2467 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2468 if (sign) {
2469 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2470 }
2471 }
2472 }
2473
Cary Clarkdf429f32017-11-08 11:44:31 -05002474 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2475 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2476 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2477 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002478 fLastVec.dot(curVec) < 0.0f) {
2479 return kBackwards_DirChange;
2480 }
2481
2482 return kStraight_DirChange;
2483 }
2484
Cary Clarkc8323aa2017-08-25 08:04:43 -04002485 bool hasBackwards() const {
2486 return fBackwards;
2487 }
caryclarkb4216502015-03-02 13:02:34 -08002488
caryclarkd3d1a982014-12-08 04:57:38 -08002489 bool isFinite() const {
2490 return fIsFinite;
2491 }
2492
caryclark5ccef572015-03-02 10:07:56 -08002493 void setCurve(bool isCurve) {
2494 fIsCurve = isCurve;
2495 }
2496
reed@google.com04863fa2011-05-15 04:08:24 +00002497private:
2498 void addVec(const SkVector& vec) {
2499 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002500 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002501 switch (dir) {
2502 case kLeft_DirChange: // fall through
2503 case kRight_DirChange:
2504 if (kInvalid_DirChange == fExpectedDir) {
2505 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002506 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2507 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002508 } else if (dir != fExpectedDir) {
2509 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002510 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002511 }
2512 fLastVec = vec;
2513 break;
2514 case kStraight_DirChange:
2515 break;
2516 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002517 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002518 // If any of the subsequent dir is non-backward, it'll be concave.
2519 // Otherwise, it's still convex.
2520 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002521 }
robertphillipsc506e302014-09-16 09:43:31 -07002522 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002523 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002524 break;
2525 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002526 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002527 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002528 }
2529 }
2530
caryclarkb4216502015-03-02 13:02:34 -08002531 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002532 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002533 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002534 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2535 // value with the current vec is deemed to be of a significant value.
2536 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002537 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002538 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002539 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002540 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002541 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002542 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002543 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002544 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002545};
2546
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002547SkPath::Convexity SkPath::internalGetConvexity() const {
2548 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002549 SkPoint pts[4];
2550 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002551 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002552
2553 int contourCount = 0;
2554 int count;
2555 Convexicator state;
2556
caryclarkd3d1a982014-12-08 04:57:38 -08002557 if (!isFinite()) {
2558 return kUnknown_Convexity;
2559 }
Brian Osman205a1262017-09-18 13:13:48 +00002560 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002561 switch (verb) {
2562 case kMove_Verb:
2563 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002564 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002565 return kConcave_Convexity;
2566 }
2567 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002568 // fall through
2569 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002570 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002571 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002572 break;
caryclark5ccef572015-03-02 10:07:56 -08002573 case kQuad_Verb:
2574 // fall through
2575 case kConic_Verb:
2576 // fall through
2577 case kCubic_Verb:
2578 count = 2 + (kCubic_Verb == verb);
2579 // As an additional enhancement, this could set curve true only
2580 // if the curve is nonlinear
2581 state.setCurve(true);
2582 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002583 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002584 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002585 state.close();
2586 count = 0;
2587 break;
2588 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002589 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002590 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002591 return kConcave_Convexity;
2592 }
2593
2594 for (int i = 1; i <= count; i++) {
2595 state.addPt(pts[i]);
2596 }
2597 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002598 if (!state.isFinite()) {
2599 return kUnknown_Convexity;
2600 }
reed@google.com04863fa2011-05-15 04:08:24 +00002601 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002602 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002603 return kConcave_Convexity;
2604 }
2605 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002606 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002607 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002608 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2609 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2610 fConvexity = Convexity::kConcave_Convexity;
2611 } else {
2612 fFirstDirection = state.getFirstDirection();
2613 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002614 }
2615 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002616}
reed@google.com69a99432012-01-10 18:00:10 +00002617
2618///////////////////////////////////////////////////////////////////////////////
2619
2620class ContourIter {
2621public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002622 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002623
2624 bool done() const { return fDone; }
2625 // if !done() then these may be called
2626 int count() const { return fCurrPtCount; }
2627 const SkPoint* pts() const { return fCurrPt; }
2628 void next();
2629
2630private:
2631 int fCurrPtCount;
2632 const SkPoint* fCurrPt;
2633 const uint8_t* fCurrVerb;
2634 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002635 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002636 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002637 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002638};
2639
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002640ContourIter::ContourIter(const SkPathRef& pathRef) {
2641 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002642 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002643 fCurrPt = pathRef.points();
2644 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002645 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002646 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002647 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002648 this->next();
2649}
2650
2651void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002652 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002653 fDone = true;
2654 }
2655 if (fDone) {
2656 return;
2657 }
2658
2659 // skip pts of prev contour
2660 fCurrPt += fCurrPtCount;
2661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002662 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002663 int ptCount = 1; // moveTo
2664 const uint8_t* verbs = fCurrVerb;
2665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002666 for (--verbs; verbs > fStopVerbs; --verbs) {
2667 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002668 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002669 goto CONTOUR_END;
2670 case SkPath::kLine_Verb:
2671 ptCount += 1;
2672 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002673 case SkPath::kConic_Verb:
2674 fCurrConicWeight += 1;
2675 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002676 case SkPath::kQuad_Verb:
2677 ptCount += 2;
2678 break;
2679 case SkPath::kCubic_Verb:
2680 ptCount += 3;
2681 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002682 case SkPath::kClose_Verb:
2683 break;
2684 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002685 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002686 break;
2687 }
2688 }
2689CONTOUR_END:
2690 fCurrPtCount = ptCount;
2691 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002692 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002693}
2694
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002695// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002696static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002697 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2698 // We may get 0 when the above subtracts underflow. We expect this to be
2699 // very rare and lazily promote to double.
2700 if (0 == cross) {
2701 double p0x = SkScalarToDouble(p0.fX);
2702 double p0y = SkScalarToDouble(p0.fY);
2703
2704 double p1x = SkScalarToDouble(p1.fX);
2705 double p1y = SkScalarToDouble(p1.fY);
2706
2707 double p2x = SkScalarToDouble(p2.fX);
2708 double p2y = SkScalarToDouble(p2.fY);
2709
2710 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2711 (p1y - p0y) * (p2x - p0x));
2712
2713 }
2714 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002715}
2716
reed@google.comc1ea60a2012-01-31 15:15:36 +00002717// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002718static int find_max_y(const SkPoint pts[], int count) {
2719 SkASSERT(count > 0);
2720 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002721 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002722 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002723 SkScalar y = pts[i].fY;
2724 if (y > max) {
2725 max = y;
2726 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002727 }
2728 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002729 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002730}
2731
reed@google.comcabaf1d2012-01-11 21:03:05 +00002732static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2733 int i = index;
2734 for (;;) {
2735 i = (i + inc) % n;
2736 if (i == index) { // we wrapped around, so abort
2737 break;
2738 }
2739 if (pts[index] != pts[i]) { // found a different point, success!
2740 break;
2741 }
2742 }
2743 return i;
2744}
2745
reed@google.comc1ea60a2012-01-31 15:15:36 +00002746/**
2747 * Starting at index, and moving forward (incrementing), find the xmin and
2748 * xmax of the contiguous points that have the same Y.
2749 */
2750static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2751 int* maxIndexPtr) {
2752 const SkScalar y = pts[index].fY;
2753 SkScalar min = pts[index].fX;
2754 SkScalar max = min;
2755 int minIndex = index;
2756 int maxIndex = index;
2757 for (int i = index + 1; i < n; ++i) {
2758 if (pts[i].fY != y) {
2759 break;
2760 }
2761 SkScalar x = pts[i].fX;
2762 if (x < min) {
2763 min = x;
2764 minIndex = i;
2765 } else if (x > max) {
2766 max = x;
2767 maxIndex = i;
2768 }
2769 }
2770 *maxIndexPtr = maxIndex;
2771 return minIndex;
2772}
2773
reed026beb52015-06-10 14:23:15 -07002774static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2775 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002776}
2777
reed@google.comac8543f2012-01-30 20:51:25 +00002778/*
2779 * We loop through all contours, and keep the computed cross-product of the
2780 * contour that contained the global y-max. If we just look at the first
2781 * contour, we may find one that is wound the opposite way (correctly) since
2782 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2783 * that is outer most (or at least has the global y-max) before we can consider
2784 * its cross product.
2785 */
reed026beb52015-06-10 14:23:15 -07002786bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002787 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2788 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002789 return true;
2790 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002791
2792 // don't want to pay the cost for computing this if it
2793 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002794 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2795 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002796 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002797 return false;
2798 }
reed@google.com69a99432012-01-10 18:00:10 +00002799
reed026beb52015-06-10 14:23:15 -07002800 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002801
reed@google.comac8543f2012-01-30 20:51:25 +00002802 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002803 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002804 SkScalar ymaxCross = 0;
2805
reed@google.com69a99432012-01-10 18:00:10 +00002806 for (; !iter.done(); iter.next()) {
2807 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002808 if (n < 3) {
2809 continue;
2810 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002811
reed@google.comcabaf1d2012-01-11 21:03:05 +00002812 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002813 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002814 int index = find_max_y(pts, n);
2815 if (pts[index].fY < ymax) {
2816 continue;
2817 }
2818
2819 // If there is more than 1 distinct point at the y-max, we take the
2820 // x-min and x-max of them and just subtract to compute the dir.
2821 if (pts[(index + 1) % n].fY == pts[index].fY) {
2822 int maxIndex;
2823 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2824 if (minIndex == maxIndex) {
2825 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002826 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002827 SkASSERT(pts[minIndex].fY == pts[index].fY);
2828 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2829 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2830 // we just subtract the indices, and let that auto-convert to
2831 // SkScalar, since we just want - or + to signal the direction.
2832 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002833 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002834 TRY_CROSSPROD:
2835 // Find a next and prev index to use for the cross-product test,
2836 // but we try to find pts that form non-zero vectors from pts[index]
2837 //
2838 // Its possible that we can't find two non-degenerate vectors, so
2839 // we have to guard our search (e.g. all the pts could be in the
2840 // same place).
2841
2842 // we pass n - 1 instead of -1 so we don't foul up % operator by
2843 // passing it a negative LH argument.
2844 int prev = find_diff_pt(pts, index, n, n - 1);
2845 if (prev == index) {
2846 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002847 continue;
2848 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002849 int next = find_diff_pt(pts, index, n, 1);
2850 SkASSERT(next != index);
2851 cross = cross_prod(pts[prev], pts[index], pts[next]);
2852 // if we get a zero and the points are horizontal, then we look at the spread in
2853 // x-direction. We really should continue to walk away from the degeneracy until
2854 // there is a divergence.
2855 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2856 // construct the subtract so we get the correct Direction below
2857 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002858 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002859 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002860
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002861 if (cross) {
2862 // record our best guess so far
2863 ymax = pts[index].fY;
2864 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002865 }
2866 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002867 if (ymaxCross) {
2868 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002869 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002870 return true;
2871 } else {
2872 return false;
2873 }
reed@google.comac8543f2012-01-30 20:51:25 +00002874}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875
2876///////////////////////////////////////////////////////////////////////////////
2877
caryclark9aacd902015-12-14 08:38:09 -08002878static bool between(SkScalar a, SkScalar b, SkScalar c) {
2879 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2880 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2881 return (a - b) * (c - b) <= 0;
2882}
2883
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2885 SkScalar t) {
2886 SkScalar A = c3 + 3*(c1 - c2) - c0;
2887 SkScalar B = 3*(c2 - c1 - c1 + c0);
2888 SkScalar C = 3*(c1 - c0);
2889 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002890 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891}
2892
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002893template <size_t N> static void find_minmax(const SkPoint pts[],
2894 SkScalar* minPtr, SkScalar* maxPtr) {
2895 SkScalar min, max;
2896 min = max = pts[0].fX;
2897 for (size_t i = 1; i < N; ++i) {
2898 min = SkMinScalar(min, pts[i].fX);
2899 max = SkMaxScalar(max, pts[i].fX);
2900 }
2901 *minPtr = min;
2902 *maxPtr = max;
2903}
2904
caryclark9cb5d752015-12-18 04:35:24 -08002905static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2906 if (start.fY == end.fY) {
2907 return between(start.fX, x, end.fX) && x != end.fX;
2908 } else {
2909 return x == start.fX && y == start.fY;
2910 }
2911}
2912
caryclark9aacd902015-12-14 08:38:09 -08002913static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002914 SkScalar y0 = pts[0].fY;
2915 SkScalar y3 = pts[3].fY;
2916
2917 int dir = 1;
2918 if (y0 > y3) {
2919 SkTSwap(y0, y3);
2920 dir = -1;
2921 }
2922 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 return 0;
2924 }
caryclark9cb5d752015-12-18 04:35:24 -08002925 if (checkOnCurve(x, y, pts[0], pts[3])) {
2926 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002927 return 0;
2928 }
caryclark9cb5d752015-12-18 04:35:24 -08002929 if (y == y3) {
2930 return 0;
2931 }
2932
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002933 // quickreject or quickaccept
2934 SkScalar min, max;
2935 find_minmax<4>(pts, &min, &max);
2936 if (x < min) {
2937 return 0;
2938 }
2939 if (x > max) {
2940 return dir;
2941 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002942
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002943 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002944 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002945 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002946 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002947 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002948 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002949 if (SkScalarNearlyEqual(xt, x)) {
2950 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2951 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002952 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002953 }
2954 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 return xt < x ? dir : 0;
2956}
2957
caryclark9aacd902015-12-14 08:38:09 -08002958static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 SkPoint dst[10];
2960 int n = SkChopCubicAtYExtrema(pts, dst);
2961 int w = 0;
2962 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002963 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 }
2965 return w;
2966}
2967
caryclark9aacd902015-12-14 08:38:09 -08002968static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2969 SkASSERT(src);
2970 SkASSERT(t >= 0 && t <= 1);
2971 SkScalar src2w = src[2] * w;
2972 SkScalar C = src[0];
2973 SkScalar A = src[4] - 2 * src2w + C;
2974 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002975 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002976}
2977
2978
2979static double conic_eval_denominator(SkScalar w, SkScalar t) {
2980 SkScalar B = 2 * (w - 1);
2981 SkScalar C = 1;
2982 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002983 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002984}
2985
2986static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2987 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 SkScalar y0 = pts[0].fY;
2989 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002990
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 int dir = 1;
2992 if (y0 > y2) {
2993 SkTSwap(y0, y2);
2994 dir = -1;
2995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 if (y < y0 || y > y2) {
2997 return 0;
2998 }
caryclark9cb5d752015-12-18 04:35:24 -08002999 if (checkOnCurve(x, y, pts[0], pts[2])) {
3000 *onCurveCount += 1;
3001 return 0;
3002 }
caryclark9aacd902015-12-14 08:38:09 -08003003 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 return 0;
3005 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003006
caryclark9aacd902015-12-14 08:38:09 -08003007 SkScalar roots[2];
3008 SkScalar A = pts[2].fY;
3009 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3010 SkScalar C = pts[0].fY;
3011 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3012 B -= C; // B = b*w - w * yCept + yCept - a
3013 C -= y;
3014 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3015 SkASSERT(n <= 1);
3016 SkScalar xt;
3017 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003018 // zero roots are returned only when y0 == y
3019 // Need [0] if dir == 1
3020 // and [2] if dir == -1
3021 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003022 } else {
3023 SkScalar t = roots[0];
3024 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3025 }
3026 if (SkScalarNearlyEqual(xt, x)) {
3027 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3028 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003029 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003030 }
3031 }
3032 return xt < x ? dir : 0;
3033}
3034
3035static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3036 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3037 if (y0 == y1) {
3038 return true;
3039 }
3040 if (y0 < y1) {
3041 return y1 <= y2;
3042 } else {
3043 return y1 >= y2;
3044 }
3045}
3046
3047static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3048 int* onCurveCount) {
3049 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003050 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003051 // If the data points are very large, the conic may not be monotonic but may also
3052 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003053 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3054 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3055 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003056 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3057 }
3058 return w;
3059}
3060
3061static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3062 SkScalar y0 = pts[0].fY;
3063 SkScalar y2 = pts[2].fY;
3064
3065 int dir = 1;
3066 if (y0 > y2) {
3067 SkTSwap(y0, y2);
3068 dir = -1;
3069 }
3070 if (y < y0 || y > y2) {
3071 return 0;
3072 }
caryclark9cb5d752015-12-18 04:35:24 -08003073 if (checkOnCurve(x, y, pts[0], pts[2])) {
3074 *onCurveCount += 1;
3075 return 0;
3076 }
caryclark9aacd902015-12-14 08:38:09 -08003077 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003078 return 0;
3079 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003080 // bounds check on X (not required. is it faster?)
3081#if 0
3082 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3083 return 0;
3084 }
3085#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003086
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003087 SkScalar roots[2];
3088 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3089 2 * (pts[1].fY - pts[0].fY),
3090 pts[0].fY - y,
3091 roots);
3092 SkASSERT(n <= 1);
3093 SkScalar xt;
3094 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003095 // zero roots are returned only when y0 == y
3096 // Need [0] if dir == 1
3097 // and [2] if dir == -1
3098 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003099 } else {
3100 SkScalar t = roots[0];
3101 SkScalar C = pts[0].fX;
3102 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3103 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003104 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003105 }
caryclark9aacd902015-12-14 08:38:09 -08003106 if (SkScalarNearlyEqual(xt, x)) {
3107 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3108 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003109 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003110 }
3111 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003112 return xt < x ? dir : 0;
3113}
3114
caryclark9aacd902015-12-14 08:38:09 -08003115static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003116 SkPoint dst[5];
3117 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003118
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003119 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3120 n = SkChopQuadAtYExtrema(pts, dst);
3121 pts = dst;
3122 }
caryclark9aacd902015-12-14 08:38:09 -08003123 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003124 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003125 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003126 }
3127 return w;
3128}
3129
caryclark9aacd902015-12-14 08:38:09 -08003130static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003131 SkScalar x0 = pts[0].fX;
3132 SkScalar y0 = pts[0].fY;
3133 SkScalar x1 = pts[1].fX;
3134 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003135
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003136 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003137
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003138 int dir = 1;
3139 if (y0 > y1) {
3140 SkTSwap(y0, y1);
3141 dir = -1;
3142 }
caryclark9aacd902015-12-14 08:38:09 -08003143 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003144 return 0;
3145 }
caryclark9cb5d752015-12-18 04:35:24 -08003146 if (checkOnCurve(x, y, pts[0], pts[1])) {
3147 *onCurveCount += 1;
3148 return 0;
3149 }
3150 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003151 return 0;
3152 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003153 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003154
caryclark9aacd902015-12-14 08:38:09 -08003155 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003156 // zero cross means the point is on the line, and since the case where
3157 // y of the query point is at the end point is handled above, we can be
3158 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003159 if (x != x1 || y != pts[1].fY) {
3160 *onCurveCount += 1;
3161 }
caryclark9aacd902015-12-14 08:38:09 -08003162 dir = 0;
3163 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003164 dir = 0;
3165 }
3166 return dir;
3167}
3168
caryclark9aacd902015-12-14 08:38:09 -08003169static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3170 SkTDArray<SkVector>* tangents) {
3171 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3172 && !between(pts[2].fY, y, pts[3].fY)) {
3173 return;
3174 }
3175 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3176 && !between(pts[2].fX, x, pts[3].fX)) {
3177 return;
3178 }
3179 SkPoint dst[10];
3180 int n = SkChopCubicAtYExtrema(pts, dst);
3181 for (int i = 0; i <= n; ++i) {
3182 SkPoint* c = &dst[i * 3];
3183 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003184 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003185 continue;
mbarbella276e6332016-05-31 14:44:01 -07003186 }
caryclark9aacd902015-12-14 08:38:09 -08003187 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3188 if (!SkScalarNearlyEqual(x, xt)) {
3189 continue;
3190 }
3191 SkVector tangent;
3192 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3193 tangents->push(tangent);
3194 }
3195}
3196
3197static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3198 SkTDArray<SkVector>* tangents) {
3199 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3200 return;
3201 }
3202 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3203 return;
3204 }
3205 SkScalar roots[2];
3206 SkScalar A = pts[2].fY;
3207 SkScalar B = pts[1].fY * w - y * w + y;
3208 SkScalar C = pts[0].fY;
3209 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3210 B -= C; // B = b*w - w * yCept + yCept - a
3211 C -= y;
3212 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3213 for (int index = 0; index < n; ++index) {
3214 SkScalar t = roots[index];
3215 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3216 if (!SkScalarNearlyEqual(x, xt)) {
3217 continue;
3218 }
3219 SkConic conic(pts, w);
3220 tangents->push(conic.evalTangentAt(t));
3221 }
3222}
3223
3224static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3225 SkTDArray<SkVector>* tangents) {
3226 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3227 return;
3228 }
3229 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3230 return;
3231 }
3232 SkScalar roots[2];
3233 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3234 2 * (pts[1].fY - pts[0].fY),
3235 pts[0].fY - y,
3236 roots);
3237 for (int index = 0; index < n; ++index) {
3238 SkScalar t = roots[index];
3239 SkScalar C = pts[0].fX;
3240 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3241 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003242 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003243 if (!SkScalarNearlyEqual(x, xt)) {
3244 continue;
3245 }
3246 tangents->push(SkEvalQuadTangentAt(pts, t));
3247 }
3248}
3249
3250static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3251 SkTDArray<SkVector>* tangents) {
3252 SkScalar y0 = pts[0].fY;
3253 SkScalar y1 = pts[1].fY;
3254 if (!between(y0, y, y1)) {
3255 return;
3256 }
3257 SkScalar x0 = pts[0].fX;
3258 SkScalar x1 = pts[1].fX;
3259 if (!between(x0, x, x1)) {
3260 return;
3261 }
3262 SkScalar dx = x1 - x0;
3263 SkScalar dy = y1 - y0;
3264 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3265 return;
3266 }
3267 SkVector v;
3268 v.set(dx, dy);
3269 tangents->push(v);
3270}
3271
reed@google.com4db592c2013-10-30 17:39:43 +00003272static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3273 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3274}
3275
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003276bool SkPath::contains(SkScalar x, SkScalar y) const {
3277 bool isInverse = this->isInverseFillType();
3278 if (this->isEmpty()) {
3279 return isInverse;
3280 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003281
reed@google.com4db592c2013-10-30 17:39:43 +00003282 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003283 return isInverse;
3284 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003285
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003286 SkPath::Iter iter(*this, true);
3287 bool done = false;
3288 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003289 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003290 do {
3291 SkPoint pts[4];
3292 switch (iter.next(pts, false)) {
3293 case SkPath::kMove_Verb:
3294 case SkPath::kClose_Verb:
3295 break;
3296 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003297 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003298 break;
3299 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003300 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003301 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003302 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003303 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003304 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003305 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003306 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003307 break;
3308 case SkPath::kDone_Verb:
3309 done = true;
3310 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003311 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003312 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003313 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3314 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3315 if (evenOddFill) {
3316 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003317 }
caryclark9aacd902015-12-14 08:38:09 -08003318 if (w) {
3319 return !isInverse;
3320 }
3321 if (onCurveCount <= 1) {
3322 return SkToBool(onCurveCount) ^ isInverse;
3323 }
3324 if ((onCurveCount & 1) || evenOddFill) {
3325 return SkToBool(onCurveCount & 1) ^ isInverse;
3326 }
halcanary9d524f22016-03-29 09:03:52 -07003327 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003328 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3329 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003330 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003331 SkTDArray<SkVector> tangents;
3332 do {
3333 SkPoint pts[4];
3334 int oldCount = tangents.count();
3335 switch (iter.next(pts, false)) {
3336 case SkPath::kMove_Verb:
3337 case SkPath::kClose_Verb:
3338 break;
3339 case SkPath::kLine_Verb:
3340 tangent_line(pts, x, y, &tangents);
3341 break;
3342 case SkPath::kQuad_Verb:
3343 tangent_quad(pts, x, y, &tangents);
3344 break;
3345 case SkPath::kConic_Verb:
3346 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3347 break;
3348 case SkPath::kCubic_Verb:
3349 tangent_cubic(pts, x, y, &tangents);
3350 break;
3351 case SkPath::kDone_Verb:
3352 done = true;
3353 break;
3354 }
3355 if (tangents.count() > oldCount) {
3356 int last = tangents.count() - 1;
3357 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003358 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003359 tangents.remove(last);
3360 } else {
3361 for (int index = 0; index < last; ++index) {
3362 const SkVector& test = tangents[index];
3363 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003364 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3365 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003366 tangents.remove(last);
3367 tangents.removeShuffle(index);
3368 break;
3369 }
3370 }
3371 }
3372 }
3373 } while (!done);
3374 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003375}
fmalitaaa0df4e2015-12-01 09:13:23 -08003376
3377int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3378 SkScalar w, SkPoint pts[], int pow2) {
3379 const SkConic conic(p0, p1, p2, w);
3380 return conic.chopIntoQuadsPOW2(pts, pow2);
3381}
bsalomonedc743a2016-06-01 09:42:31 -07003382
3383bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3384 unsigned* start) {
3385 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3386 return false;
3387 }
3388 SkPath::RawIter iter(path);
3389 SkPoint verbPts[4];
3390 SkPath::Verb v;
3391 SkPoint rectPts[5];
3392 int rectPtCnt = 0;
3393 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3394 switch (v) {
3395 case SkPath::kMove_Verb:
3396 if (0 != rectPtCnt) {
3397 return false;
3398 }
3399 rectPts[0] = verbPts[0];
3400 ++rectPtCnt;
3401 break;
3402 case SkPath::kLine_Verb:
3403 if (5 == rectPtCnt) {
3404 return false;
3405 }
3406 rectPts[rectPtCnt] = verbPts[1];
3407 ++rectPtCnt;
3408 break;
3409 case SkPath::kClose_Verb:
3410 if (4 == rectPtCnt) {
3411 rectPts[4] = rectPts[0];
3412 rectPtCnt = 5;
3413 }
3414 break;
3415 default:
3416 return false;
3417 }
3418 }
3419 if (rectPtCnt < 5) {
3420 return false;
3421 }
3422 if (rectPts[0] != rectPts[4]) {
3423 return false;
3424 }
bsalomon057ae8a2016-07-24 05:37:26 -07003425 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3426 // and pts 1 and 2 the opposite vertical or horizontal edge).
3427 bool vec03IsVertical;
3428 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3429 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3430 // Make sure it has non-zero width and height
3431 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003432 return false;
3433 }
bsalomon057ae8a2016-07-24 05:37:26 -07003434 vec03IsVertical = true;
3435 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3436 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3437 // Make sure it has non-zero width and height
3438 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3439 return false;
3440 }
3441 vec03IsVertical = false;
3442 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003443 return false;
3444 }
bsalomon057ae8a2016-07-24 05:37:26 -07003445 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3446 // set if it is on the bottom edge.
3447 unsigned sortFlags =
3448 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3449 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3450 switch (sortFlags) {
3451 case 0b00:
3452 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3453 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3454 *start = 0;
3455 break;
3456 case 0b01:
3457 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3458 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3459 *start = 1;
3460 break;
3461 case 0b10:
3462 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3463 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3464 *start = 3;
3465 break;
3466 case 0b11:
3467 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3468 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3469 *start = 2;
3470 break;
bsalomonedc743a2016-06-01 09:42:31 -07003471 }
bsalomonedc743a2016-06-01 09:42:31 -07003472 return true;
3473}
bsalomon21af9ca2016-08-25 12:29:23 -07003474
3475void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3476 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3477 SkASSERT(!oval.isEmpty());
3478 SkASSERT(sweepAngle);
3479
3480 path->reset();
3481 path->setIsVolatile(true);
3482 path->setFillType(SkPath::kWinding_FillType);
3483 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3484 path->addOval(oval);
3485 return;
3486 }
3487 if (useCenter) {
3488 path->moveTo(oval.centerX(), oval.centerY());
3489 }
3490 // Arc to mods at 360 and drawArc is not supposed to.
3491 bool forceMoveTo = !useCenter;
3492 while (sweepAngle <= -360.f) {
3493 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3494 startAngle -= 180.f;
3495 path->arcTo(oval, startAngle, -180.f, false);
3496 startAngle -= 180.f;
3497 forceMoveTo = false;
3498 sweepAngle += 360.f;
3499 }
3500 while (sweepAngle >= 360.f) {
3501 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3502 startAngle += 180.f;
3503 path->arcTo(oval, startAngle, 180.f, false);
3504 startAngle += 180.f;
3505 forceMoveTo = false;
3506 sweepAngle -= 360.f;
3507 }
3508 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3509 if (useCenter) {
3510 path->close();
3511 }
3512}
Mike Reed0d7dac82017-02-02 17:45:56 -08003513
3514///////////////////////////////////////////////////////////////////////////////////////////////////
3515#include "SkNx.h"
3516
3517static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3518 SkScalar ts[2];
3519 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3520 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3521 SkASSERT(n >= 0 && n <= 2);
3522 for (int i = 0; i < n; ++i) {
3523 extremas[i] = SkEvalQuadAt(src, ts[i]);
3524 }
3525 extremas[n] = src[2];
3526 return n + 1;
3527}
3528
3529static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3530 SkConic conic(src[0], src[1], src[2], w);
3531 SkScalar ts[2];
3532 int n = conic.findXExtrema(ts);
3533 n += conic.findYExtrema(&ts[n]);
3534 SkASSERT(n >= 0 && n <= 2);
3535 for (int i = 0; i < n; ++i) {
3536 extremas[i] = conic.evalAt(ts[i]);
3537 }
3538 extremas[n] = src[2];
3539 return n + 1;
3540}
3541
3542static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3543 SkScalar ts[4];
3544 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3545 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3546 SkASSERT(n >= 0 && n <= 4);
3547 for (int i = 0; i < n; ++i) {
3548 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3549 }
3550 extremas[n] = src[3];
3551 return n + 1;
3552}
3553
Mike Reed8d3196b2017-02-03 11:34:13 -05003554SkRect SkPath::computeTightBounds() const {
3555 if (0 == this->countVerbs()) {
3556 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003557 }
3558
Mike Reed8d3196b2017-02-03 11:34:13 -05003559 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3560 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003561 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003562
Mike Reed0d7dac82017-02-02 17:45:56 -08003563 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3564 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003565 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003566
3567 // initial with the first MoveTo, so we don't have to check inside the switch
3568 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003569 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003570 for (;;) {
3571 int count = 0;
3572 switch (iter.next(pts)) {
3573 case SkPath::kMove_Verb:
3574 extremas[0] = pts[0];
3575 count = 1;
3576 break;
3577 case SkPath::kLine_Verb:
3578 extremas[0] = pts[1];
3579 count = 1;
3580 break;
3581 case SkPath::kQuad_Verb:
3582 count = compute_quad_extremas(pts, extremas);
3583 break;
3584 case SkPath::kConic_Verb:
3585 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3586 break;
3587 case SkPath::kCubic_Verb:
3588 count = compute_cubic_extremas(pts, extremas);
3589 break;
3590 case SkPath::kClose_Verb:
3591 break;
3592 case SkPath::kDone_Verb:
3593 goto DONE;
3594 }
3595 for (int i = 0; i < count; ++i) {
3596 Sk2s tmp = from_point(extremas[i]);
3597 min = Sk2s::Min(min, tmp);
3598 max = Sk2s::Max(max, tmp);
3599 }
3600 }
3601DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003602 SkRect bounds;
3603 min.store((SkPoint*)&bounds.fLeft);
3604 max.store((SkPoint*)&bounds.fRight);
3605 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003606}
Cary Clarkdf429f32017-11-08 11:44:31 -05003607
3608bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3609 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3610}
3611
3612bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3613 const SkPoint& p3, bool exact) {
3614 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3615 SkPointPriv::EqualsWithinTolerance(p2, p3);
3616}
3617
3618bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3619 const SkPoint& p3, const SkPoint& p4, bool exact) {
3620 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3621 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3622 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3623 SkPointPriv::EqualsWithinTolerance(p3, p4);
3624}