blob: 422c2b911d49d0a8fc39f9903ba45ca7eeed44c4 [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
Cary Clark1acd3c72017-12-08 11:37:01 -05001420#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001421 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001422#else
1423 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1424 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1425#endif
caryclark55d49052016-01-23 05:07:04 -08001426 SkScalar thetaWidth = thetaArc / segments;
1427 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1428 if (!SkScalarIsFinite(t)) {
1429 return;
1430 }
1431 SkScalar startTheta = theta1;
1432 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001433#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1434 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1435 return scalar == SkScalarFloorToScalar(scalar);
1436 };
1437 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1438 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1439 scalar_is_integer(x) && scalar_is_integer(y);
1440#endif
caryclark55d49052016-01-23 05:07:04 -08001441 for (int i = 0; i < segments; ++i) {
1442 SkScalar endTheta = startTheta + thetaWidth;
1443 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1444
1445 unitPts[1].set(cosEndTheta, sinEndTheta);
1446 unitPts[1] += centerPoint;
1447 unitPts[0] = unitPts[1];
1448 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1449 SkPoint mapped[2];
1450 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001451 /*
1452 Computing the arc width introduces rounding errors that cause arcs to start
1453 outside their marks. A round rect may lose convexity as a result. If the input
1454 values are on integers, place the conic on integers as well.
1455 */
1456#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1457 if (expectIntegers) {
1458 SkScalar* mappedScalars = &mapped[0].fX;
1459 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1460 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1461 }
1462 }
1463#endif
caryclark55d49052016-01-23 05:07:04 -08001464 this->conicTo(mapped[0], mapped[1], w);
1465 startTheta = endTheta;
1466 }
1467}
1468
1469void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1470 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1471 SkPoint currentPoint;
1472 this->getLastPt(&currentPoint);
1473 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1474}
1475
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001476void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477 if (oval.isEmpty() || 0 == sweepAngle) {
1478 return;
1479 }
1480
1481 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1482
1483 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001484 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1485 // See SkPath::addOval() docs.
1486 SkScalar startOver90 = startAngle / 90.f;
1487 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1488 SkScalar error = startOver90 - startOver90I;
1489 if (SkScalarNearlyEqual(error, 0)) {
1490 // Index 1 is at startAngle == 0.
1491 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1492 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1493 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1494 (unsigned) startIndex);
1495 return;
1496 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 }
bsalomon1978ce22016-05-31 10:42:16 -07001498 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499}
1500
1501/*
1502 Need to handle the case when the angle is sharp, and our computed end-points
1503 for the arc go behind pt1 and/or p2...
1504*/
reedc7789042015-01-29 12:59:11 -08001505void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001506 if (radius == 0) {
1507 this->lineTo(x1, y1);
1508 return;
1509 }
1510
1511 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001512
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 // need to know our prev pt so we can construct tangent vectors
1514 {
1515 SkPoint start;
1516 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001517 // Handle degenerate cases by adding a line to the first point and
1518 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 before.setNormalize(x1 - start.fX, y1 - start.fY);
1520 after.setNormalize(x2 - x1, y2 - y1);
1521 }
reed@google.comabf15c12011-01-18 20:35:51 +00001522
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 SkScalar cosh = SkPoint::DotProduct(before, after);
1524 SkScalar sinh = SkPoint::CrossProduct(before, after);
1525
1526 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001527 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 return;
1529 }
reed@google.comabf15c12011-01-18 20:35:51 +00001530
Mike Reeda99b6ce2017-02-04 11:04:26 -05001531 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532
Mike Reeda99b6ce2017-02-04 11:04:26 -05001533 SkScalar xx = x1 - dist * before.fX;
1534 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001535 after.setLength(dist);
1536 this->lineTo(xx, yy);
1537 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1538 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539}
1540
1541///////////////////////////////////////////////////////////////////////////////
1542
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001543void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 SkMatrix matrix;
1545
1546 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001547 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548}
1549
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001550void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001551 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001553 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 SkPoint pts[4];
1555 Verb verb;
1556
Cary Clark9480d822017-10-19 18:01:13 -04001557 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001558 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 while ((verb = iter.next(pts)) != kDone_Verb) {
1560 switch (verb) {
1561 case kMove_Verb:
1562 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001563 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1564 injectMoveToIfNeeded(); // In case last contour is closed
1565 this->lineTo(pts[0]);
1566 } else {
1567 this->moveTo(pts[0]);
1568 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 break;
1570 case kLine_Verb:
1571 proc(matrix, &pts[1], &pts[1], 1);
1572 this->lineTo(pts[1]);
1573 break;
1574 case kQuad_Verb:
1575 proc(matrix, &pts[1], &pts[1], 2);
1576 this->quadTo(pts[1], pts[2]);
1577 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001578 case kConic_Verb:
1579 proc(matrix, &pts[1], &pts[1], 2);
1580 this->conicTo(pts[1], pts[2], iter.conicWeight());
1581 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001582 case kCubic_Verb:
1583 proc(matrix, &pts[1], &pts[1], 3);
1584 this->cubicTo(pts[1], pts[2], pts[3]);
1585 break;
1586 case kClose_Verb:
1587 this->close();
1588 break;
1589 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001590 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001592 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593 }
1594}
1595
1596///////////////////////////////////////////////////////////////////////////////
1597
reed@google.com277c3f82013-05-31 15:17:50 +00001598static int pts_in_verb(unsigned verb) {
1599 static const uint8_t gPtsInVerb[] = {
1600 1, // kMove
1601 1, // kLine
1602 2, // kQuad
1603 2, // kConic
1604 3, // kCubic
1605 0, // kClose
1606 0 // kDone
1607 };
1608
1609 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1610 return gPtsInVerb[verb];
1611}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613// ignore the last point of the 1st contour
1614void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001615 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1616 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 return;
1618 }
caryclark51c56782016-11-07 05:09:28 -08001619 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1620 SkASSERT(verbsEnd[0] == kMove_Verb);
1621 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1622 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623
caryclark51c56782016-11-07 05:09:28 -08001624 while (verbs < verbsEnd) {
1625 uint8_t v = *verbs++;
1626 pts -= pts_in_verb(v);
1627 switch (v) {
1628 case kMove_Verb:
1629 // if the path has multiple contours, stop after reversing the last
1630 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001632 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 break;
1634 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001635 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001637 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001638 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001639 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001641 this->cubicTo(pts[2], pts[1], pts[0]);
1642 break;
1643 case kClose_Verb:
1644 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 break;
1646 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001647 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 break;
1649 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 }
1651}
1652
reed@google.com63d73742012-01-10 15:33:12 +00001653void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001654 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001656 const SkPoint* pts = src.fPathRef->pointsEnd();
1657 // we will iterator through src's verbs backwards
1658 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1659 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001660 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001661
1662 bool needMove = true;
1663 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001664 while (verbs < verbsEnd) {
1665 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001666 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001667
1668 if (needMove) {
1669 --pts;
1670 this->moveTo(pts->fX, pts->fY);
1671 needMove = false;
1672 }
1673 pts -= n;
1674 switch (v) {
1675 case kMove_Verb:
1676 if (needClose) {
1677 this->close();
1678 needClose = false;
1679 }
1680 needMove = true;
1681 pts += 1; // so we see the point in "if (needMove)" above
1682 break;
1683 case kLine_Verb:
1684 this->lineTo(pts[0]);
1685 break;
1686 case kQuad_Verb:
1687 this->quadTo(pts[1], pts[0]);
1688 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001689 case kConic_Verb:
1690 this->conicTo(pts[1], pts[0], *--conicWeights);
1691 break;
reed@google.com63d73742012-01-10 15:33:12 +00001692 case kCubic_Verb:
1693 this->cubicTo(pts[2], pts[1], pts[0]);
1694 break;
1695 case kClose_Verb:
1696 needClose = true;
1697 break;
1698 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001699 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001700 }
1701 }
1702}
1703
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704///////////////////////////////////////////////////////////////////////////////
1705
1706void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1707 SkMatrix matrix;
1708
1709 matrix.setTranslate(dx, dy);
1710 this->transform(matrix, dst);
1711}
1712
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1714 int level = 2) {
1715 if (--level >= 0) {
1716 SkPoint tmp[7];
1717
1718 SkChopCubicAtHalf(pts, tmp);
1719 subdivide_cubic_to(path, &tmp[0], level);
1720 subdivide_cubic_to(path, &tmp[3], level);
1721 } else {
1722 path->cubicTo(pts[1], pts[2], pts[3]);
1723 }
1724}
1725
1726void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1727 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001728 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 dst = (SkPath*)this;
1730 }
1731
tomhudson@google.com8d430182011-06-06 19:11:19 +00001732 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 SkPath tmp;
1734 tmp.fFillType = fFillType;
1735
1736 SkPath::Iter iter(*this, false);
1737 SkPoint pts[4];
1738 SkPath::Verb verb;
1739
reed@google.com4a3b7142012-05-16 17:16:46 +00001740 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 switch (verb) {
1742 case kMove_Verb:
1743 tmp.moveTo(pts[0]);
1744 break;
1745 case kLine_Verb:
1746 tmp.lineTo(pts[1]);
1747 break;
1748 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001749 // promote the quad to a conic
1750 tmp.conicTo(pts[1], pts[2],
1751 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001753 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001754 tmp.conicTo(pts[1], pts[2],
1755 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001756 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757 case kCubic_Verb:
1758 subdivide_cubic_to(&tmp, pts);
1759 break;
1760 case kClose_Verb:
1761 tmp.close();
1762 break;
1763 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001764 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 break;
1766 }
1767 }
1768
1769 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001770 SkPathRef::Editor ed(&dst->fPathRef);
1771 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001772 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001774 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1775
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001778 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001779 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001781
reed026beb52015-06-10 14:23:15 -07001782 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1783 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001784 } else {
1785 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001786 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1787 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001788 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001789 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1790 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001791 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001792 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001793 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001794 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001795 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001796 }
1797 }
1798
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 SkDEBUGCODE(dst->validate();)
1800 }
1801}
1802
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803///////////////////////////////////////////////////////////////////////////////
1804///////////////////////////////////////////////////////////////////////////////
1805
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001807 kEmptyContour_SegmentState, // The current contour is empty. We may be
1808 // starting processing or we may have just
1809 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1811 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1812 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813};
1814
1815SkPath::Iter::Iter() {
1816#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001817 fPts = nullptr;
1818 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001821 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822#endif
1823 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001824 fVerbs = nullptr;
1825 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 fNeedClose = false;
1827}
1828
1829SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1830 this->setPath(path, forceClose);
1831}
1832
1833void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001834 fPts = path.fPathRef->points();
1835 fVerbs = path.fPathRef->verbs();
1836 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001837 fConicWeights = path.fPathRef->conicWeights();
1838 if (fConicWeights) {
1839 fConicWeights -= 1; // begin one behind
1840 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001841 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001842 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 fForceClose = SkToU8(forceClose);
1844 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001845 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846}
1847
1848bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001849 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 return false;
1851 }
1852 if (fForceClose) {
1853 return true;
1854 }
1855
1856 const uint8_t* verbs = fVerbs;
1857 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001858
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001859 if (kMove_Verb == *(verbs - 1)) {
1860 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 }
1862
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 while (verbs > stop) {
1864 // verbs points one beyond the current verb, decrement first.
1865 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 if (kMove_Verb == v) {
1867 break;
1868 }
1869 if (kClose_Verb == v) {
1870 return true;
1871 }
1872 }
1873 return false;
1874}
1875
1876SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001877 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001878 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001879 // A special case: if both points are NaN, SkPoint::operation== returns
1880 // false, but the iterator expects that they are treated as the same.
1881 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001882 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1883 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001884 return kClose_Verb;
1885 }
1886
reed@google.com9e25dbf2012-05-15 17:05:38 +00001887 pts[0] = fLastPt;
1888 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889 fLastPt = fMoveTo;
1890 fCloseLine = true;
1891 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001892 } else {
1893 pts[0] = fMoveTo;
1894 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896}
1897
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001899 if (fSegmentState == kAfterMove_SegmentState) {
1900 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1905 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001906 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908}
1909
caryclarke8c56662015-07-14 11:19:26 -07001910void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 // We need to step over anything that will not move the current draw point
1912 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001913 const uint8_t* lastMoveVerb = nullptr;
1914 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001915 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 SkPoint lastPt = fLastPt;
1917 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 switch (verb) {
1920 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 // Keep a record of this most recent move
1922 lastMoveVerb = fVerbs;
1923 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001924 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001925 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001926 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 fPts++;
1928 break;
1929
1930 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001931 // A close when we are in a segment is always valid except when it
1932 // follows a move which follows a segment.
1933 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 return;
1935 }
1936 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 break;
1939
1940 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001941 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 if (lastMoveVerb) {
1943 fVerbs = lastMoveVerb;
1944 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001945 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 return;
1947 }
1948 return;
1949 }
1950 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001951 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 fPts++;
1953 break;
1954
reed@google.com277c3f82013-05-31 15:17:50 +00001955 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001957 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 if (lastMoveVerb) {
1959 fVerbs = lastMoveVerb;
1960 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001961 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 return;
1963 }
1964 return;
1965 }
1966 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001967 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001969 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 break;
1971
1972 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001973 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 if (lastMoveVerb) {
1975 fVerbs = lastMoveVerb;
1976 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001977 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 return;
1979 }
1980 return;
1981 }
1982 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001983 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 fPts += 3;
1985 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001986
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001988 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001989 }
1990 }
1991}
1992
reed@google.com4a3b7142012-05-16 17:16:46 +00001993SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001994 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 // Close the curve if requested and if there is some curve to close
1998 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 return kLine_Verb;
2001 }
2002 fNeedClose = false;
2003 return kClose_Verb;
2004 }
2005 return kDone_Verb;
2006 }
2007
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002008 // fVerbs is one beyond the current verb, decrement first
2009 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002010 const SkPoint* SK_RESTRICT srcPts = fPts;
2011 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012
2013 switch (verb) {
2014 case kMove_Verb:
2015 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002016 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017 verb = this->autoClose(pts);
2018 if (verb == kClose_Verb) {
2019 fNeedClose = false;
2020 }
2021 return (Verb)verb;
2022 }
2023 if (fVerbs == fVerbStop) { // might be a trailing moveto
2024 return kDone_Verb;
2025 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002026 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002027 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002029 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002030 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 fNeedClose = fForceClose;
2032 break;
2033 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002034 pts[0] = this->cons_moveTo();
2035 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 fLastPt = srcPts[0];
2037 fCloseLine = false;
2038 srcPts += 1;
2039 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002040 case kConic_Verb:
2041 fConicWeights += 1;
2042 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002044 pts[0] = this->cons_moveTo();
2045 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 fLastPt = srcPts[1];
2047 srcPts += 2;
2048 break;
2049 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002050 pts[0] = this->cons_moveTo();
2051 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 fLastPt = srcPts[2];
2053 srcPts += 3;
2054 break;
2055 case kClose_Verb:
2056 verb = this->autoClose(pts);
2057 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002058 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059 } else {
2060 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002061 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002063 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 break;
2065 }
2066 fPts = srcPts;
2067 return (Verb)verb;
2068}
2069
2070///////////////////////////////////////////////////////////////////////////////
2071
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002073 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074*/
2075
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002076size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2077 SkRect oval;
2078 SkRRect rrect;
2079 bool isCCW;
2080 unsigned start;
2081 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2082 rrect.setOval(oval);
2083 // Convert to rrect start indices.
2084 start *= 2;
2085 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2086 return false;
2087 }
2088 if (!storage) {
2089 // packed header, rrect, start index.
2090 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002091 }
2092
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002093 SkWBuffer buffer(storage);
2094 // Rewrite header's first direction based on rrect direction.
2095 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2096 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2097 packedHeader |= firstDir << kDirection_SerializationShift;
2098 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2099 buffer.write32(packedHeader);
2100 rrect.writeToBuffer(&buffer);
2101 buffer.write32(SkToS32(start));
2102 buffer.padToAlign4();
2103 return buffer.pos();
2104}
2105
2106size_t SkPath::writeToMemory(void* storage) const {
2107 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002108
robertphillips@google.com466310d2013-12-03 16:43:54 +00002109 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002110 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002111 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002112 (fIsVolatile << kIsVolatile_SerializationShift) |
2113 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002114 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2115 return bytes;
2116 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002117
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002118 SkWBuffer buffer(storage);
2119
2120 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2121 if (nullptr == storage) {
2122 // packed header, pathref, start index
2123 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2124 return SkAlign4(byteCount);
2125 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002126 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002127 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002128
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002129 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002130
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002131 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002132 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133}
2134
Mike Reed41a930f2017-07-26 17:33:44 -04002135sk_sp<SkData> SkPath::serialize() const {
2136 size_t size = this->writeToMemory(nullptr);
2137 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2138 this->writeToMemory(data->writable_data());
2139 return data;
2140}
2141
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002142size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002143 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002144
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002145 int32_t packed;
2146 if (!buffer.readS32(&packed)) {
2147 return 0;
2148 }
2149
reed8f086022015-06-11 14:22:19 -07002150 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002151 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2152 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2153 if (version >= kPathPrivTypeEnumVersion) {
2154 SerializationType type =
2155 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2156 switch (type) {
2157 case SerializationType::kRRect: {
2158 Direction rrectDir;
2159 SkRRect rrect;
2160 int32_t start;
2161 switch (dir) {
2162 case SkPathPriv::kCW_FirstDirection:
2163 rrectDir = kCW_Direction;
2164 break;
2165 case SkPathPriv::kCCW_FirstDirection:
2166 rrectDir = kCCW_Direction;
2167 break;
2168 default:
2169 return 0;
2170 }
2171 if (!rrect.readFromBuffer(&buffer)) {
2172 return 0;
2173 }
2174 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2175 return 0;
2176 }
2177 this->reset();
2178 this->addRRect(rrect, rrectDir, SkToUInt(start));
2179 this->setFillType(fillType);
2180 buffer.skipToAlign4();
2181 return buffer.pos();
2182 }
2183 case SerializationType::kGeneral:
2184 // Fall through to general path deserialization
2185 break;
2186 default:
2187 return 0;
2188 }
2189 }
caryclark69006412016-02-17 12:16:27 -08002190 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2191 return 0;
2192 }
mtklein1b249332015-07-07 12:21:21 -07002193
Brian Salomon7072e222017-11-29 15:12:45 -05002194 // These are written into the serialized data but we no longer use them in the deserialized
2195 // path. If convexity is corrupted it may cause the GPU backend to make incorrect
2196 // rendering choices, possibly crashing. We set them to unknown so that they'll be recomputed if
2197 // requested.
2198 fConvexity = kUnknown_Convexity;
2199 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2200
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002201 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002202 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002203 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002204 if (!pathRef) {
2205 return 0;
2206 }
2207
2208 fPathRef.reset(pathRef);
2209 SkDEBUGCODE(this->validate();)
2210 buffer.skipToAlign4();
ajumaf8aec582016-01-13 13:46:31 -08002211 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212}
2213
2214///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215
Ben Wagner4d1955c2017-03-10 13:08:15 -05002216#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002217#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002218#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002219
reed@google.com51bbe752013-01-17 22:07:50 +00002220static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002221 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002222 str->append(label);
2223 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002224
reed@google.com51bbe752013-01-17 22:07:50 +00002225 const SkScalar* values = &pts[0].fX;
2226 count *= 2;
2227
2228 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002229 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002230 if (i < count - 1) {
2231 str->append(", ");
2232 }
2233 }
reed@google.com277c3f82013-05-31 15:17:50 +00002234 if (conicWeight >= 0) {
2235 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002236 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002237 }
caryclark08fa28c2014-10-23 13:08:56 -07002238 str->append(");");
reede05fed02014-12-15 07:59:53 -08002239 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002240 str->append(" // ");
2241 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002242 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002243 if (i < count - 1) {
2244 str->append(", ");
2245 }
2246 }
2247 if (conicWeight >= 0) {
2248 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002249 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002250 }
2251 }
2252 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002253}
2254
caryclarke9562592014-09-15 09:26:09 -07002255void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002256 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002257 Iter iter(*this, forceClose);
2258 SkPoint pts[4];
2259 Verb verb;
2260
reed@google.com51bbe752013-01-17 22:07:50 +00002261 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002262 char const * const gFillTypeStrs[] = {
2263 "Winding",
2264 "EvenOdd",
2265 "InverseWinding",
2266 "InverseEvenOdd",
2267 };
2268 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2269 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002270 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002271 switch (verb) {
2272 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002273 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002274 break;
2275 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002276 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002277 break;
2278 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002279 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002280 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002281 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002282 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002283 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002284 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002285 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002286 break;
2287 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002288 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002289 break;
2290 default:
2291 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2292 verb = kDone_Verb; // stop the loop
2293 break;
2294 }
caryclark1049f122015-04-20 08:31:59 -07002295 if (!wStream && builder.size()) {
2296 SkDebugf("%s", builder.c_str());
2297 builder.reset();
2298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002299 }
caryclark66a5d8b2014-06-24 08:30:15 -07002300 if (wStream) {
2301 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002302 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002303}
2304
reed@android.come522ca52009-11-23 20:10:41 +00002305void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002306 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002307}
2308
2309void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002310 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002311}
2312
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002313
Cary Clark0461e9f2017-08-25 15:13:38 -04002314bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002315 if ((fFillType & ~3) != 0) {
2316 return false;
2317 }
reed@google.comabf15c12011-01-18 20:35:51 +00002318
djsollen@google.com077348c2012-10-22 20:23:32 +00002319#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002320 if (!fBoundsIsDirty) {
2321 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002322
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002323 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002324 if (SkToBool(fIsFinite) != isFinite) {
2325 return false;
2326 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002327
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002328 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002329 // if we're empty, fBounds may be empty but translated, so we can't
2330 // necessarily compare to bounds directly
2331 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2332 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002333 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2334 return false;
2335 }
reed@android.come522ca52009-11-23 20:10:41 +00002336 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002337 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002338 if (!fBounds.isEmpty()) {
2339 return false;
2340 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002341 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002342 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002343 if (!fBounds.contains(bounds)) {
2344 return false;
2345 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002346 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002347 }
reed@android.come522ca52009-11-23 20:10:41 +00002348 }
2349 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002350#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002351 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002352}
reed@android.come522ca52009-11-23 20:10:41 +00002353
reed@google.com04863fa2011-05-15 04:08:24 +00002354///////////////////////////////////////////////////////////////////////////////
2355
reed@google.com0b7b9822011-05-16 12:29:27 +00002356static int sign(SkScalar x) { return x < 0; }
2357#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002358
robertphillipsc506e302014-09-16 09:43:31 -07002359enum DirChange {
2360 kLeft_DirChange,
2361 kRight_DirChange,
2362 kStraight_DirChange,
2363 kBackwards_DirChange,
2364
2365 kInvalid_DirChange
2366};
2367
2368
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002369static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002370 // The error epsilon was empirically derived; worse case round rects
2371 // with a mid point outset by 2x float epsilon in tests had an error
2372 // of 12.
2373 const int epsilon = 16;
2374 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2375 return false;
2376 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002377 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002378 int aBits = SkFloatAs2sCompliment(compA);
2379 int bBits = SkFloatAs2sCompliment(compB);
2380 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002381}
2382
caryclarkb4216502015-03-02 13:02:34 -08002383static bool approximately_zero_when_compared_to(double x, double y) {
2384 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002385}
2386
caryclarkb4216502015-03-02 13:02:34 -08002387
reed@google.com04863fa2011-05-15 04:08:24 +00002388// only valid for a single contour
2389struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002390 Convexicator()
2391 : fPtCount(0)
2392 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002393 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002394 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002395 , fIsCurve(false)
2396 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002397 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002398 // warnings
djsollen2f124632016-04-29 13:53:05 -07002399 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002400 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002401 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002402 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002403 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002404
2405 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002406 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 }
2408
2409 SkPath::Convexity getConvexity() const { return fConvexity; }
2410
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002411 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002412 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002413
reed@google.com04863fa2011-05-15 04:08:24 +00002414 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002415 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002416 return;
2417 }
2418
2419 if (0 == fPtCount) {
2420 fCurrPt = pt;
2421 ++fPtCount;
2422 } else {
2423 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002424 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002425 if (!SkScalarIsFinite(lengthSqd)) {
2426 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002427 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002428 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002429 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002430 fCurrPt = pt;
2431 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002432 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002433 } else {
2434 SkASSERT(fPtCount > 2);
2435 this->addVec(vec);
2436 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002437
reed@google.com85b6e392011-05-15 20:25:17 +00002438 int sx = sign(vec.fX);
2439 int sy = sign(vec.fY);
2440 fDx += (sx != fSx);
2441 fDy += (sy != fSy);
2442 fSx = sx;
2443 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002444
reed@google.com85b6e392011-05-15 20:25:17 +00002445 if (fDx > 3 || fDy > 3) {
2446 fConvexity = SkPath::kConcave_Convexity;
2447 }
reed@google.com04863fa2011-05-15 04:08:24 +00002448 }
2449 }
2450 }
2451
2452 void close() {
2453 if (fPtCount > 2) {
2454 this->addVec(fFirstVec);
2455 }
2456 }
2457
caryclarkb4216502015-03-02 13:02:34 -08002458 DirChange directionChange(const SkVector& curVec) {
2459 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2460
2461 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2462 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2463 largest = SkTMax(largest, -smallest);
2464
2465 if (!almost_equal(largest, largest + cross)) {
2466 int sign = SkScalarSignAsInt(cross);
2467 if (sign) {
2468 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2469 }
2470 }
2471
2472 if (cross) {
2473 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2474 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2475 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2476 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2477 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2478 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2479 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2480 if (sign) {
2481 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2482 }
2483 }
2484 }
2485
Cary Clarkdf429f32017-11-08 11:44:31 -05002486 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2487 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2488 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2489 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002490 fLastVec.dot(curVec) < 0.0f) {
2491 return kBackwards_DirChange;
2492 }
2493
2494 return kStraight_DirChange;
2495 }
2496
Cary Clarkc8323aa2017-08-25 08:04:43 -04002497 bool hasBackwards() const {
2498 return fBackwards;
2499 }
caryclarkb4216502015-03-02 13:02:34 -08002500
caryclarkd3d1a982014-12-08 04:57:38 -08002501 bool isFinite() const {
2502 return fIsFinite;
2503 }
2504
caryclark5ccef572015-03-02 10:07:56 -08002505 void setCurve(bool isCurve) {
2506 fIsCurve = isCurve;
2507 }
2508
reed@google.com04863fa2011-05-15 04:08:24 +00002509private:
2510 void addVec(const SkVector& vec) {
2511 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002512 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002513 switch (dir) {
2514 case kLeft_DirChange: // fall through
2515 case kRight_DirChange:
2516 if (kInvalid_DirChange == fExpectedDir) {
2517 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002518 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2519 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002520 } else if (dir != fExpectedDir) {
2521 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002522 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002523 }
2524 fLastVec = vec;
2525 break;
2526 case kStraight_DirChange:
2527 break;
2528 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002529 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002530 // If any of the subsequent dir is non-backward, it'll be concave.
2531 // Otherwise, it's still convex.
2532 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002533 }
robertphillipsc506e302014-09-16 09:43:31 -07002534 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002535 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002536 break;
2537 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002538 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002539 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002540 }
2541 }
2542
caryclarkb4216502015-03-02 13:02:34 -08002543 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002544 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002545 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002546 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2547 // value with the current vec is deemed to be of a significant value.
2548 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002549 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002550 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002551 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002552 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002553 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002554 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002555 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002556 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002557};
2558
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002559SkPath::Convexity SkPath::internalGetConvexity() const {
2560 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002561 SkPoint pts[4];
2562 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002563 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002564
2565 int contourCount = 0;
2566 int count;
2567 Convexicator state;
2568
caryclarkd3d1a982014-12-08 04:57:38 -08002569 if (!isFinite()) {
2570 return kUnknown_Convexity;
2571 }
Brian Osman205a1262017-09-18 13:13:48 +00002572 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002573 switch (verb) {
2574 case kMove_Verb:
2575 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002576 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002577 return kConcave_Convexity;
2578 }
2579 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002580 // fall through
2581 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002582 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002583 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002584 break;
caryclark5ccef572015-03-02 10:07:56 -08002585 case kQuad_Verb:
2586 // fall through
2587 case kConic_Verb:
2588 // fall through
2589 case kCubic_Verb:
2590 count = 2 + (kCubic_Verb == verb);
2591 // As an additional enhancement, this could set curve true only
2592 // if the curve is nonlinear
2593 state.setCurve(true);
2594 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002595 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002596 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002597 state.close();
2598 count = 0;
2599 break;
2600 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002601 SkDEBUGFAIL("bad verb");
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
2606 for (int i = 1; i <= count; i++) {
2607 state.addPt(pts[i]);
2608 }
2609 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002610 if (!state.isFinite()) {
2611 return kUnknown_Convexity;
2612 }
reed@google.com04863fa2011-05-15 04:08:24 +00002613 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002614 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002615 return kConcave_Convexity;
2616 }
2617 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002618 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002619 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002620 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2621 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2622 fConvexity = Convexity::kConcave_Convexity;
2623 } else {
2624 fFirstDirection = state.getFirstDirection();
2625 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002626 }
2627 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002628}
reed@google.com69a99432012-01-10 18:00:10 +00002629
2630///////////////////////////////////////////////////////////////////////////////
2631
2632class ContourIter {
2633public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002634 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002635
2636 bool done() const { return fDone; }
2637 // if !done() then these may be called
2638 int count() const { return fCurrPtCount; }
2639 const SkPoint* pts() const { return fCurrPt; }
2640 void next();
2641
2642private:
2643 int fCurrPtCount;
2644 const SkPoint* fCurrPt;
2645 const uint8_t* fCurrVerb;
2646 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002647 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002648 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002649 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002650};
2651
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002652ContourIter::ContourIter(const SkPathRef& pathRef) {
2653 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002654 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002655 fCurrPt = pathRef.points();
2656 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002657 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002658 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002659 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002660 this->next();
2661}
2662
2663void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002664 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002665 fDone = true;
2666 }
2667 if (fDone) {
2668 return;
2669 }
2670
2671 // skip pts of prev contour
2672 fCurrPt += fCurrPtCount;
2673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002674 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002675 int ptCount = 1; // moveTo
2676 const uint8_t* verbs = fCurrVerb;
2677
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002678 for (--verbs; verbs > fStopVerbs; --verbs) {
2679 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002680 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002681 goto CONTOUR_END;
2682 case SkPath::kLine_Verb:
2683 ptCount += 1;
2684 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002685 case SkPath::kConic_Verb:
2686 fCurrConicWeight += 1;
2687 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002688 case SkPath::kQuad_Verb:
2689 ptCount += 2;
2690 break;
2691 case SkPath::kCubic_Verb:
2692 ptCount += 3;
2693 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002694 case SkPath::kClose_Verb:
2695 break;
2696 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002697 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002698 break;
2699 }
2700 }
2701CONTOUR_END:
2702 fCurrPtCount = ptCount;
2703 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002704 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002705}
2706
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002707// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002708static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002709 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2710 // We may get 0 when the above subtracts underflow. We expect this to be
2711 // very rare and lazily promote to double.
2712 if (0 == cross) {
2713 double p0x = SkScalarToDouble(p0.fX);
2714 double p0y = SkScalarToDouble(p0.fY);
2715
2716 double p1x = SkScalarToDouble(p1.fX);
2717 double p1y = SkScalarToDouble(p1.fY);
2718
2719 double p2x = SkScalarToDouble(p2.fX);
2720 double p2y = SkScalarToDouble(p2.fY);
2721
2722 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2723 (p1y - p0y) * (p2x - p0x));
2724
2725 }
2726 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002727}
2728
reed@google.comc1ea60a2012-01-31 15:15:36 +00002729// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002730static int find_max_y(const SkPoint pts[], int count) {
2731 SkASSERT(count > 0);
2732 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002733 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002734 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002735 SkScalar y = pts[i].fY;
2736 if (y > max) {
2737 max = y;
2738 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002739 }
2740 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002741 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002742}
2743
reed@google.comcabaf1d2012-01-11 21:03:05 +00002744static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2745 int i = index;
2746 for (;;) {
2747 i = (i + inc) % n;
2748 if (i == index) { // we wrapped around, so abort
2749 break;
2750 }
2751 if (pts[index] != pts[i]) { // found a different point, success!
2752 break;
2753 }
2754 }
2755 return i;
2756}
2757
reed@google.comc1ea60a2012-01-31 15:15:36 +00002758/**
2759 * Starting at index, and moving forward (incrementing), find the xmin and
2760 * xmax of the contiguous points that have the same Y.
2761 */
2762static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2763 int* maxIndexPtr) {
2764 const SkScalar y = pts[index].fY;
2765 SkScalar min = pts[index].fX;
2766 SkScalar max = min;
2767 int minIndex = index;
2768 int maxIndex = index;
2769 for (int i = index + 1; i < n; ++i) {
2770 if (pts[i].fY != y) {
2771 break;
2772 }
2773 SkScalar x = pts[i].fX;
2774 if (x < min) {
2775 min = x;
2776 minIndex = i;
2777 } else if (x > max) {
2778 max = x;
2779 maxIndex = i;
2780 }
2781 }
2782 *maxIndexPtr = maxIndex;
2783 return minIndex;
2784}
2785
reed026beb52015-06-10 14:23:15 -07002786static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2787 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002788}
2789
reed@google.comac8543f2012-01-30 20:51:25 +00002790/*
2791 * We loop through all contours, and keep the computed cross-product of the
2792 * contour that contained the global y-max. If we just look at the first
2793 * contour, we may find one that is wound the opposite way (correctly) since
2794 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2795 * that is outer most (or at least has the global y-max) before we can consider
2796 * its cross product.
2797 */
reed026beb52015-06-10 14:23:15 -07002798bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002799 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2800 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002801 return true;
2802 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002803
2804 // don't want to pay the cost for computing this if it
2805 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002806 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2807 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002808 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002809 return false;
2810 }
reed@google.com69a99432012-01-10 18:00:10 +00002811
reed026beb52015-06-10 14:23:15 -07002812 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002813
reed@google.comac8543f2012-01-30 20:51:25 +00002814 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002815 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002816 SkScalar ymaxCross = 0;
2817
reed@google.com69a99432012-01-10 18:00:10 +00002818 for (; !iter.done(); iter.next()) {
2819 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002820 if (n < 3) {
2821 continue;
2822 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002823
reed@google.comcabaf1d2012-01-11 21:03:05 +00002824 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002825 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002826 int index = find_max_y(pts, n);
2827 if (pts[index].fY < ymax) {
2828 continue;
2829 }
2830
2831 // If there is more than 1 distinct point at the y-max, we take the
2832 // x-min and x-max of them and just subtract to compute the dir.
2833 if (pts[(index + 1) % n].fY == pts[index].fY) {
2834 int maxIndex;
2835 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2836 if (minIndex == maxIndex) {
2837 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002838 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002839 SkASSERT(pts[minIndex].fY == pts[index].fY);
2840 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2841 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2842 // we just subtract the indices, and let that auto-convert to
2843 // SkScalar, since we just want - or + to signal the direction.
2844 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002845 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002846 TRY_CROSSPROD:
2847 // Find a next and prev index to use for the cross-product test,
2848 // but we try to find pts that form non-zero vectors from pts[index]
2849 //
2850 // Its possible that we can't find two non-degenerate vectors, so
2851 // we have to guard our search (e.g. all the pts could be in the
2852 // same place).
2853
2854 // we pass n - 1 instead of -1 so we don't foul up % operator by
2855 // passing it a negative LH argument.
2856 int prev = find_diff_pt(pts, index, n, n - 1);
2857 if (prev == index) {
2858 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002859 continue;
2860 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002861 int next = find_diff_pt(pts, index, n, 1);
2862 SkASSERT(next != index);
2863 cross = cross_prod(pts[prev], pts[index], pts[next]);
2864 // if we get a zero and the points are horizontal, then we look at the spread in
2865 // x-direction. We really should continue to walk away from the degeneracy until
2866 // there is a divergence.
2867 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2868 // construct the subtract so we get the correct Direction below
2869 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002870 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002871 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002872
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002873 if (cross) {
2874 // record our best guess so far
2875 ymax = pts[index].fY;
2876 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002877 }
2878 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002879 if (ymaxCross) {
2880 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002881 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002882 return true;
2883 } else {
2884 return false;
2885 }
reed@google.comac8543f2012-01-30 20:51:25 +00002886}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002887
2888///////////////////////////////////////////////////////////////////////////////
2889
caryclark9aacd902015-12-14 08:38:09 -08002890static bool between(SkScalar a, SkScalar b, SkScalar c) {
2891 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2892 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2893 return (a - b) * (c - b) <= 0;
2894}
2895
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002896static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2897 SkScalar t) {
2898 SkScalar A = c3 + 3*(c1 - c2) - c0;
2899 SkScalar B = 3*(c2 - c1 - c1 + c0);
2900 SkScalar C = 3*(c1 - c0);
2901 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002902 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002903}
2904
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002905template <size_t N> static void find_minmax(const SkPoint pts[],
2906 SkScalar* minPtr, SkScalar* maxPtr) {
2907 SkScalar min, max;
2908 min = max = pts[0].fX;
2909 for (size_t i = 1; i < N; ++i) {
2910 min = SkMinScalar(min, pts[i].fX);
2911 max = SkMaxScalar(max, pts[i].fX);
2912 }
2913 *minPtr = min;
2914 *maxPtr = max;
2915}
2916
caryclark9cb5d752015-12-18 04:35:24 -08002917static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2918 if (start.fY == end.fY) {
2919 return between(start.fX, x, end.fX) && x != end.fX;
2920 } else {
2921 return x == start.fX && y == start.fY;
2922 }
2923}
2924
caryclark9aacd902015-12-14 08:38:09 -08002925static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002926 SkScalar y0 = pts[0].fY;
2927 SkScalar y3 = pts[3].fY;
2928
2929 int dir = 1;
2930 if (y0 > y3) {
2931 SkTSwap(y0, y3);
2932 dir = -1;
2933 }
2934 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 return 0;
2936 }
caryclark9cb5d752015-12-18 04:35:24 -08002937 if (checkOnCurve(x, y, pts[0], pts[3])) {
2938 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002939 return 0;
2940 }
caryclark9cb5d752015-12-18 04:35:24 -08002941 if (y == y3) {
2942 return 0;
2943 }
2944
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 // quickreject or quickaccept
2946 SkScalar min, max;
2947 find_minmax<4>(pts, &min, &max);
2948 if (x < min) {
2949 return 0;
2950 }
2951 if (x > max) {
2952 return dir;
2953 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002954
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002956 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002957 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002958 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002959 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002960 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002961 if (SkScalarNearlyEqual(xt, x)) {
2962 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2963 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002964 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002965 }
2966 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 return xt < x ? dir : 0;
2968}
2969
caryclark9aacd902015-12-14 08:38:09 -08002970static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 SkPoint dst[10];
2972 int n = SkChopCubicAtYExtrema(pts, dst);
2973 int w = 0;
2974 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002975 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 }
2977 return w;
2978}
2979
caryclark9aacd902015-12-14 08:38:09 -08002980static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2981 SkASSERT(src);
2982 SkASSERT(t >= 0 && t <= 1);
2983 SkScalar src2w = src[2] * w;
2984 SkScalar C = src[0];
2985 SkScalar A = src[4] - 2 * src2w + C;
2986 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002987 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002988}
2989
2990
2991static double conic_eval_denominator(SkScalar w, SkScalar t) {
2992 SkScalar B = 2 * (w - 1);
2993 SkScalar C = 1;
2994 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002995 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002996}
2997
2998static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2999 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 SkScalar y0 = pts[0].fY;
3001 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003002
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 int dir = 1;
3004 if (y0 > y2) {
3005 SkTSwap(y0, y2);
3006 dir = -1;
3007 }
caryclark9aacd902015-12-14 08:38:09 -08003008 if (y < y0 || y > y2) {
3009 return 0;
3010 }
caryclark9cb5d752015-12-18 04:35:24 -08003011 if (checkOnCurve(x, y, pts[0], pts[2])) {
3012 *onCurveCount += 1;
3013 return 0;
3014 }
caryclark9aacd902015-12-14 08:38:09 -08003015 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 return 0;
3017 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003018
caryclark9aacd902015-12-14 08:38:09 -08003019 SkScalar roots[2];
3020 SkScalar A = pts[2].fY;
3021 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3022 SkScalar C = pts[0].fY;
3023 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3024 B -= C; // B = b*w - w * yCept + yCept - a
3025 C -= y;
3026 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3027 SkASSERT(n <= 1);
3028 SkScalar xt;
3029 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003030 // zero roots are returned only when y0 == y
3031 // Need [0] if dir == 1
3032 // and [2] if dir == -1
3033 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003034 } else {
3035 SkScalar t = roots[0];
3036 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3037 }
3038 if (SkScalarNearlyEqual(xt, x)) {
3039 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3040 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003041 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003042 }
3043 }
3044 return xt < x ? dir : 0;
3045}
3046
3047static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3048 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3049 if (y0 == y1) {
3050 return true;
3051 }
3052 if (y0 < y1) {
3053 return y1 <= y2;
3054 } else {
3055 return y1 >= y2;
3056 }
3057}
3058
3059static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3060 int* onCurveCount) {
3061 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003062 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003063 // If the data points are very large, the conic may not be monotonic but may also
3064 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003065 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3066 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3067 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003068 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3069 }
3070 return w;
3071}
3072
3073static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3074 SkScalar y0 = pts[0].fY;
3075 SkScalar y2 = pts[2].fY;
3076
3077 int dir = 1;
3078 if (y0 > y2) {
3079 SkTSwap(y0, y2);
3080 dir = -1;
3081 }
3082 if (y < y0 || y > y2) {
3083 return 0;
3084 }
caryclark9cb5d752015-12-18 04:35:24 -08003085 if (checkOnCurve(x, y, pts[0], pts[2])) {
3086 *onCurveCount += 1;
3087 return 0;
3088 }
caryclark9aacd902015-12-14 08:38:09 -08003089 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003090 return 0;
3091 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003092 // bounds check on X (not required. is it faster?)
3093#if 0
3094 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3095 return 0;
3096 }
3097#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003098
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003099 SkScalar roots[2];
3100 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3101 2 * (pts[1].fY - pts[0].fY),
3102 pts[0].fY - y,
3103 roots);
3104 SkASSERT(n <= 1);
3105 SkScalar xt;
3106 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003107 // zero roots are returned only when y0 == y
3108 // Need [0] if dir == 1
3109 // and [2] if dir == -1
3110 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003111 } else {
3112 SkScalar t = roots[0];
3113 SkScalar C = pts[0].fX;
3114 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3115 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003116 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003117 }
caryclark9aacd902015-12-14 08:38:09 -08003118 if (SkScalarNearlyEqual(xt, x)) {
3119 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3120 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003121 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003122 }
3123 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003124 return xt < x ? dir : 0;
3125}
3126
caryclark9aacd902015-12-14 08:38:09 -08003127static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003128 SkPoint dst[5];
3129 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003130
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003131 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3132 n = SkChopQuadAtYExtrema(pts, dst);
3133 pts = dst;
3134 }
caryclark9aacd902015-12-14 08:38:09 -08003135 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003136 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003137 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003138 }
3139 return w;
3140}
3141
caryclark9aacd902015-12-14 08:38:09 -08003142static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003143 SkScalar x0 = pts[0].fX;
3144 SkScalar y0 = pts[0].fY;
3145 SkScalar x1 = pts[1].fX;
3146 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003147
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003148 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003149
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150 int dir = 1;
3151 if (y0 > y1) {
3152 SkTSwap(y0, y1);
3153 dir = -1;
3154 }
caryclark9aacd902015-12-14 08:38:09 -08003155 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003156 return 0;
3157 }
caryclark9cb5d752015-12-18 04:35:24 -08003158 if (checkOnCurve(x, y, pts[0], pts[1])) {
3159 *onCurveCount += 1;
3160 return 0;
3161 }
3162 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003163 return 0;
3164 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003165 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003166
caryclark9aacd902015-12-14 08:38:09 -08003167 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003168 // zero cross means the point is on the line, and since the case where
3169 // y of the query point is at the end point is handled above, we can be
3170 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003171 if (x != x1 || y != pts[1].fY) {
3172 *onCurveCount += 1;
3173 }
caryclark9aacd902015-12-14 08:38:09 -08003174 dir = 0;
3175 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 dir = 0;
3177 }
3178 return dir;
3179}
3180
caryclark9aacd902015-12-14 08:38:09 -08003181static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3182 SkTDArray<SkVector>* tangents) {
3183 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3184 && !between(pts[2].fY, y, pts[3].fY)) {
3185 return;
3186 }
3187 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3188 && !between(pts[2].fX, x, pts[3].fX)) {
3189 return;
3190 }
3191 SkPoint dst[10];
3192 int n = SkChopCubicAtYExtrema(pts, dst);
3193 for (int i = 0; i <= n; ++i) {
3194 SkPoint* c = &dst[i * 3];
3195 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003196 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003197 continue;
mbarbella276e6332016-05-31 14:44:01 -07003198 }
caryclark9aacd902015-12-14 08:38:09 -08003199 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3200 if (!SkScalarNearlyEqual(x, xt)) {
3201 continue;
3202 }
3203 SkVector tangent;
3204 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3205 tangents->push(tangent);
3206 }
3207}
3208
3209static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3210 SkTDArray<SkVector>* tangents) {
3211 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3212 return;
3213 }
3214 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3215 return;
3216 }
3217 SkScalar roots[2];
3218 SkScalar A = pts[2].fY;
3219 SkScalar B = pts[1].fY * w - y * w + y;
3220 SkScalar C = pts[0].fY;
3221 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3222 B -= C; // B = b*w - w * yCept + yCept - a
3223 C -= y;
3224 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3225 for (int index = 0; index < n; ++index) {
3226 SkScalar t = roots[index];
3227 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3228 if (!SkScalarNearlyEqual(x, xt)) {
3229 continue;
3230 }
3231 SkConic conic(pts, w);
3232 tangents->push(conic.evalTangentAt(t));
3233 }
3234}
3235
3236static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3237 SkTDArray<SkVector>* tangents) {
3238 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3239 return;
3240 }
3241 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3242 return;
3243 }
3244 SkScalar roots[2];
3245 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3246 2 * (pts[1].fY - pts[0].fY),
3247 pts[0].fY - y,
3248 roots);
3249 for (int index = 0; index < n; ++index) {
3250 SkScalar t = roots[index];
3251 SkScalar C = pts[0].fX;
3252 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3253 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003254 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003255 if (!SkScalarNearlyEqual(x, xt)) {
3256 continue;
3257 }
3258 tangents->push(SkEvalQuadTangentAt(pts, t));
3259 }
3260}
3261
3262static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3263 SkTDArray<SkVector>* tangents) {
3264 SkScalar y0 = pts[0].fY;
3265 SkScalar y1 = pts[1].fY;
3266 if (!between(y0, y, y1)) {
3267 return;
3268 }
3269 SkScalar x0 = pts[0].fX;
3270 SkScalar x1 = pts[1].fX;
3271 if (!between(x0, x, x1)) {
3272 return;
3273 }
3274 SkScalar dx = x1 - x0;
3275 SkScalar dy = y1 - y0;
3276 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3277 return;
3278 }
3279 SkVector v;
3280 v.set(dx, dy);
3281 tangents->push(v);
3282}
3283
reed@google.com4db592c2013-10-30 17:39:43 +00003284static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3285 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3286}
3287
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003288bool SkPath::contains(SkScalar x, SkScalar y) const {
3289 bool isInverse = this->isInverseFillType();
3290 if (this->isEmpty()) {
3291 return isInverse;
3292 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003293
reed@google.com4db592c2013-10-30 17:39:43 +00003294 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003295 return isInverse;
3296 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003297
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003298 SkPath::Iter iter(*this, true);
3299 bool done = false;
3300 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003301 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003302 do {
3303 SkPoint pts[4];
3304 switch (iter.next(pts, false)) {
3305 case SkPath::kMove_Verb:
3306 case SkPath::kClose_Verb:
3307 break;
3308 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003309 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003310 break;
3311 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003312 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003313 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003314 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003315 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003316 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003317 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003318 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003319 break;
3320 case SkPath::kDone_Verb:
3321 done = true;
3322 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003323 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003324 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003325 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3326 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3327 if (evenOddFill) {
3328 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003329 }
caryclark9aacd902015-12-14 08:38:09 -08003330 if (w) {
3331 return !isInverse;
3332 }
3333 if (onCurveCount <= 1) {
3334 return SkToBool(onCurveCount) ^ isInverse;
3335 }
3336 if ((onCurveCount & 1) || evenOddFill) {
3337 return SkToBool(onCurveCount & 1) ^ isInverse;
3338 }
halcanary9d524f22016-03-29 09:03:52 -07003339 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003340 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3341 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003342 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003343 SkTDArray<SkVector> tangents;
3344 do {
3345 SkPoint pts[4];
3346 int oldCount = tangents.count();
3347 switch (iter.next(pts, false)) {
3348 case SkPath::kMove_Verb:
3349 case SkPath::kClose_Verb:
3350 break;
3351 case SkPath::kLine_Verb:
3352 tangent_line(pts, x, y, &tangents);
3353 break;
3354 case SkPath::kQuad_Verb:
3355 tangent_quad(pts, x, y, &tangents);
3356 break;
3357 case SkPath::kConic_Verb:
3358 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3359 break;
3360 case SkPath::kCubic_Verb:
3361 tangent_cubic(pts, x, y, &tangents);
3362 break;
3363 case SkPath::kDone_Verb:
3364 done = true;
3365 break;
3366 }
3367 if (tangents.count() > oldCount) {
3368 int last = tangents.count() - 1;
3369 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003370 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003371 tangents.remove(last);
3372 } else {
3373 for (int index = 0; index < last; ++index) {
3374 const SkVector& test = tangents[index];
3375 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003376 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3377 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003378 tangents.remove(last);
3379 tangents.removeShuffle(index);
3380 break;
3381 }
3382 }
3383 }
3384 }
3385 } while (!done);
3386 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003387}
fmalitaaa0df4e2015-12-01 09:13:23 -08003388
3389int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3390 SkScalar w, SkPoint pts[], int pow2) {
3391 const SkConic conic(p0, p1, p2, w);
3392 return conic.chopIntoQuadsPOW2(pts, pow2);
3393}
bsalomonedc743a2016-06-01 09:42:31 -07003394
3395bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3396 unsigned* start) {
3397 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3398 return false;
3399 }
3400 SkPath::RawIter iter(path);
3401 SkPoint verbPts[4];
3402 SkPath::Verb v;
3403 SkPoint rectPts[5];
3404 int rectPtCnt = 0;
3405 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3406 switch (v) {
3407 case SkPath::kMove_Verb:
3408 if (0 != rectPtCnt) {
3409 return false;
3410 }
3411 rectPts[0] = verbPts[0];
3412 ++rectPtCnt;
3413 break;
3414 case SkPath::kLine_Verb:
3415 if (5 == rectPtCnt) {
3416 return false;
3417 }
3418 rectPts[rectPtCnt] = verbPts[1];
3419 ++rectPtCnt;
3420 break;
3421 case SkPath::kClose_Verb:
3422 if (4 == rectPtCnt) {
3423 rectPts[4] = rectPts[0];
3424 rectPtCnt = 5;
3425 }
3426 break;
3427 default:
3428 return false;
3429 }
3430 }
3431 if (rectPtCnt < 5) {
3432 return false;
3433 }
3434 if (rectPts[0] != rectPts[4]) {
3435 return false;
3436 }
bsalomon057ae8a2016-07-24 05:37:26 -07003437 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3438 // and pts 1 and 2 the opposite vertical or horizontal edge).
3439 bool vec03IsVertical;
3440 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3441 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3442 // Make sure it has non-zero width and height
3443 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003444 return false;
3445 }
bsalomon057ae8a2016-07-24 05:37:26 -07003446 vec03IsVertical = true;
3447 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3448 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3449 // Make sure it has non-zero width and height
3450 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3451 return false;
3452 }
3453 vec03IsVertical = false;
3454 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003455 return false;
3456 }
bsalomon057ae8a2016-07-24 05:37:26 -07003457 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3458 // set if it is on the bottom edge.
3459 unsigned sortFlags =
3460 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3461 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3462 switch (sortFlags) {
3463 case 0b00:
3464 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3465 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3466 *start = 0;
3467 break;
3468 case 0b01:
3469 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3470 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3471 *start = 1;
3472 break;
3473 case 0b10:
3474 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3475 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3476 *start = 3;
3477 break;
3478 case 0b11:
3479 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3480 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3481 *start = 2;
3482 break;
bsalomonedc743a2016-06-01 09:42:31 -07003483 }
bsalomonedc743a2016-06-01 09:42:31 -07003484 return true;
3485}
bsalomon21af9ca2016-08-25 12:29:23 -07003486
3487void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3488 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3489 SkASSERT(!oval.isEmpty());
3490 SkASSERT(sweepAngle);
3491
3492 path->reset();
3493 path->setIsVolatile(true);
3494 path->setFillType(SkPath::kWinding_FillType);
3495 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3496 path->addOval(oval);
3497 return;
3498 }
3499 if (useCenter) {
3500 path->moveTo(oval.centerX(), oval.centerY());
3501 }
3502 // Arc to mods at 360 and drawArc is not supposed to.
3503 bool forceMoveTo = !useCenter;
3504 while (sweepAngle <= -360.f) {
3505 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3506 startAngle -= 180.f;
3507 path->arcTo(oval, startAngle, -180.f, false);
3508 startAngle -= 180.f;
3509 forceMoveTo = false;
3510 sweepAngle += 360.f;
3511 }
3512 while (sweepAngle >= 360.f) {
3513 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3514 startAngle += 180.f;
3515 path->arcTo(oval, startAngle, 180.f, false);
3516 startAngle += 180.f;
3517 forceMoveTo = false;
3518 sweepAngle -= 360.f;
3519 }
3520 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3521 if (useCenter) {
3522 path->close();
3523 }
3524}
Mike Reed0d7dac82017-02-02 17:45:56 -08003525
3526///////////////////////////////////////////////////////////////////////////////////////////////////
3527#include "SkNx.h"
3528
3529static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3530 SkScalar ts[2];
3531 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3532 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3533 SkASSERT(n >= 0 && n <= 2);
3534 for (int i = 0; i < n; ++i) {
3535 extremas[i] = SkEvalQuadAt(src, ts[i]);
3536 }
3537 extremas[n] = src[2];
3538 return n + 1;
3539}
3540
3541static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3542 SkConic conic(src[0], src[1], src[2], w);
3543 SkScalar ts[2];
3544 int n = conic.findXExtrema(ts);
3545 n += conic.findYExtrema(&ts[n]);
3546 SkASSERT(n >= 0 && n <= 2);
3547 for (int i = 0; i < n; ++i) {
3548 extremas[i] = conic.evalAt(ts[i]);
3549 }
3550 extremas[n] = src[2];
3551 return n + 1;
3552}
3553
3554static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3555 SkScalar ts[4];
3556 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3557 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3558 SkASSERT(n >= 0 && n <= 4);
3559 for (int i = 0; i < n; ++i) {
3560 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3561 }
3562 extremas[n] = src[3];
3563 return n + 1;
3564}
3565
Mike Reed8d3196b2017-02-03 11:34:13 -05003566SkRect SkPath::computeTightBounds() const {
3567 if (0 == this->countVerbs()) {
3568 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003569 }
3570
Mike Reed8d3196b2017-02-03 11:34:13 -05003571 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3572 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003573 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003574
Mike Reed0d7dac82017-02-02 17:45:56 -08003575 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3576 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003577 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003578
3579 // initial with the first MoveTo, so we don't have to check inside the switch
3580 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003581 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003582 for (;;) {
3583 int count = 0;
3584 switch (iter.next(pts)) {
3585 case SkPath::kMove_Verb:
3586 extremas[0] = pts[0];
3587 count = 1;
3588 break;
3589 case SkPath::kLine_Verb:
3590 extremas[0] = pts[1];
3591 count = 1;
3592 break;
3593 case SkPath::kQuad_Verb:
3594 count = compute_quad_extremas(pts, extremas);
3595 break;
3596 case SkPath::kConic_Verb:
3597 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3598 break;
3599 case SkPath::kCubic_Verb:
3600 count = compute_cubic_extremas(pts, extremas);
3601 break;
3602 case SkPath::kClose_Verb:
3603 break;
3604 case SkPath::kDone_Verb:
3605 goto DONE;
3606 }
3607 for (int i = 0; i < count; ++i) {
3608 Sk2s tmp = from_point(extremas[i]);
3609 min = Sk2s::Min(min, tmp);
3610 max = Sk2s::Max(max, tmp);
3611 }
3612 }
3613DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003614 SkRect bounds;
3615 min.store((SkPoint*)&bounds.fLeft);
3616 max.store((SkPoint*)&bounds.fRight);
3617 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003618}
Cary Clarkdf429f32017-11-08 11:44:31 -05003619
3620bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3621 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3622}
3623
3624bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3625 const SkPoint& p3, bool exact) {
3626 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3627 SkPointPriv::EqualsWithinTolerance(p2, p3);
3628}
3629
3630bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3631 const SkPoint& p3, const SkPoint& p4, bool exact) {
3632 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3633 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3634 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3635 SkPointPriv::EqualsWithinTolerance(p3, p4);
3636}