blob: ab8bf2e1fca3a52ef56b4a539311cd46e0f5eeb3 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000019
Mike Reeda99b6ce2017-02-04 11:04:26 -050020static float poly_eval(float A, float B, float C, float t) {
21 return (A * t + B) * t + C;
22}
23
24static float poly_eval(float A, float B, float C, float D, float t) {
25 return ((A * t + B) * t + C) * t + D;
26}
27
reed@android.com8a1c16f2008-12-17 15:59:43 +000028////////////////////////////////////////////////////////////////////////////
29
reed@google.com3563c9e2011-11-14 19:34:57 +000030/**
31 * Path.bounds is defined to be the bounds of all the control points.
32 * If we called bounds.join(r) we would skip r if r was empty, which breaks
33 * our promise. Hence we have a custom joiner that doesn't look at emptiness
34 */
35static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
36 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
37 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
38 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
39 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
40}
41
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000042static bool is_degenerate(const SkPath& path) {
43 SkPath::Iter iter(path, false);
44 SkPoint pts[4];
45 return SkPath::kDone_Verb == iter.next(pts);
46}
47
bsalomon@google.com30c174b2012-11-13 14:36:42 +000048class SkAutoDisableDirectionCheck {
49public:
50 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070051 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052 }
53
54 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070055 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000056 }
57
58private:
reed026beb52015-06-10 14:23:15 -070059 SkPath* fPath;
60 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000061};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000062#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000063
reed@android.com8a1c16f2008-12-17 15:59:43 +000064/* This guy's constructor/destructor bracket a path editing operation. It is
65 used when we know the bounds of the amount we are going to add to the path
66 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000067
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000069 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000070 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000071
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000072 It also notes if the path was originally degenerate, and if so, sets
73 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000074 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 */
76class SkAutoPathBoundsUpdate {
77public:
78 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
79 this->init(path);
80 }
81
82 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
83 SkScalar right, SkScalar bottom) {
84 fRect.set(left, top, right, bottom);
85 this->init(path);
86 }
reed@google.comabf15c12011-01-18 20:35:51 +000087
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000089 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
90 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050091 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000092 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 }
94 }
reed@google.comabf15c12011-01-18 20:35:51 +000095
reed@android.com8a1c16f2008-12-17 15:59:43 +000096private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000097 SkPath* fPath;
98 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000101 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000102
reed@android.com6b82d1a2009-06-03 02:35:01 +0000103 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000104 // Cannot use fRect for our bounds unless we know it is sorted
105 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000107 // Mark the path's bounds as dirty if (1) they are, or (2) the path
108 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000111 if (fHasValidBounds && !fEmpty) {
112 joinNoEmptyChecks(&fRect, fPath->getBounds());
113 }
114 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 }
116};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000117#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119////////////////////////////////////////////////////////////////////////////
120
121/*
122 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000123 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
125
126 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000127 1. If we encounter degenerate segments, remove them
128 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
129 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
130 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131*/
132
133////////////////////////////////////////////////////////////////////////////
134
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135// flag to require a moveTo if we begin with something else, like lineTo etc.
136#define INITIAL_LASTMOVETOINDEX_VALUE ~0
137
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000138SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800139 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700141 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000142}
143
144void SkPath::resetFields() {
145 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000146 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000148 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700149 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000150
151 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700152 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000153}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000156 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 this->copyFields(that);
158 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159}
160
161SkPath::~SkPath() {
162 SkDEBUGCODE(this->validate();)
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165SkPath& SkPath::operator=(const SkPath& that) {
166 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 if (this != &that) {
169 fPathRef.reset(SkRef(that.fPathRef.get()));
170 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 }
172 SkDEBUGCODE(this->validate();)
173 return *this;
174}
175
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176void SkPath::copyFields(const SkPath& that) {
177 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000178 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700180 fIsVolatile = that.fIsVolatile;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400181
182 // Non-atomic assignment of atomic values.
183 fConvexity .store(that.fConvexity .load());
184 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185}
186
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000187bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000188 // note: don't need to look at isConvex or bounds, since just comparing the
189 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000190 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000191 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000192}
193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700196 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000197 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700199 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400200
201 // Non-atomic swaps of atomic values.
202 Convexity c = fConvexity.load();
203 fConvexity.store(that.fConvexity.load());
204 that.fConvexity.store(c);
205
206 uint8_t fd = fFirstDirection.load();
207 fFirstDirection.store(that.fFirstDirection.load());
208 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 }
210}
211
caryclark8e7b19d2016-02-18 04:11:48 -0800212bool SkPath::isInterpolatable(const SkPath& compare) const {
213 int count = fPathRef->countVerbs();
214 if (count != compare.fPathRef->countVerbs()) {
215 return false;
216 }
217 if (!count) {
218 return true;
219 }
220 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
221 count)) {
222 return false;
223 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800224 return !fPathRef->countWeights() ||
225 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800226 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
227}
228
229bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
230 int verbCount = fPathRef->countVerbs();
231 if (verbCount != ending.fPathRef->countVerbs()) {
232 return false;
233 }
caryclarka1105382016-02-18 06:13:25 -0800234 if (!verbCount) {
235 return true;
236 }
caryclark8e7b19d2016-02-18 04:11:48 -0800237 out->reset();
238 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700239 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800240 return true;
241}
242
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000243static inline bool check_edge_against_rect(const SkPoint& p0,
244 const SkPoint& p1,
245 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700246 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 const SkPoint* edgeBegin;
248 SkVector v;
reed026beb52015-06-10 14:23:15 -0700249 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 v = p1 - p0;
251 edgeBegin = &p0;
252 } else {
253 v = p0 - p1;
254 edgeBegin = &p1;
255 }
256 if (v.fX || v.fY) {
257 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500258 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
259 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
260 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
261 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
263 return false;
264 }
265 }
266 return true;
267}
268
269bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
270 // This only handles non-degenerate convex paths currently.
271 if (kConvex_Convexity != this->getConvexity()) {
272 return false;
273 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000274
reed026beb52015-06-10 14:23:15 -0700275 SkPathPriv::FirstDirection direction;
276 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 return false;
278 }
279
280 SkPoint firstPt;
281 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500282 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000283 SkPath::Verb verb;
284 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400285 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288
Lee Salzmana19f0242017-01-12 13:06:21 -0500289 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 int nextPt = -1;
291 switch (verb) {
292 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 SkDEBUGCODE(++moveCnt);
295 firstPt = prevPt = pts[0];
296 break;
297 case kLine_Verb:
298 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000299 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400300 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 break;
302 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000303 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000304 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400305 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000306 nextPt = 2;
307 break;
308 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000309 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400310 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311 nextPt = 3;
312 break;
313 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000314 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315 break;
316 default:
317 SkDEBUGFAIL("unknown verb");
318 }
319 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800320 if (SkPath::kConic_Verb == verb) {
321 SkConic orig;
322 orig.set(pts, iter.conicWeight());
323 SkPoint quadPts[5];
324 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800325 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800326
327 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
328 return false;
329 }
330 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
331 return false;
332 }
333 } else {
334 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
335 return false;
336 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000337 }
338 prevPt = pts[nextPt];
339 }
340 }
341
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400342 if (segmentCount) {
343 return check_edge_against_rect(prevPt, firstPt, rect, direction);
344 }
345 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000346}
347
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000348uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000349 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800350#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400351 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
352 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000353#endif
354 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000355}
djsollen@google.come63793a2012-03-21 15:39:03 +0000356
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357void SkPath::reset() {
358 SkDEBUGCODE(this->validate();)
359
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000361 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362}
363
364void SkPath::rewind() {
365 SkDEBUGCODE(this->validate();)
366
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000367 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000368 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369}
370
fsb1475b02016-01-20 09:51:07 -0800371bool SkPath::isLastContourClosed() const {
372 int verbCount = fPathRef->countVerbs();
373 if (0 == verbCount) {
374 return false;
375 }
376 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
377}
378
reed@google.com7e6c4d12012-05-10 14:05:43 +0000379bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000380 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000382 if (2 == verbCount) {
383 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
384 if (kLine_Verb == fPathRef->atVerb(1)) {
385 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000386 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000387 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000388 line[0] = pts[0];
389 line[1] = pts[1];
390 }
391 return true;
392 }
393 }
394 return false;
395}
396
caryclark@google.comf1316942011-07-26 19:54:45 +0000397/*
398 Determines if path is a rect by keeping track of changes in direction
399 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000400
caryclark@google.comf1316942011-07-26 19:54:45 +0000401 The direction is computed such that:
402 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000403 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000405 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000406
caryclark@google.comf1316942011-07-26 19:54:45 +0000407A rectangle cycles up/right/down/left or up/left/down/right.
408
409The test fails if:
410 The path is closed, and followed by a line.
411 A second move creates a new endpoint.
412 A diagonal line is parsed.
413 There's more than four changes of direction.
414 There's a discontinuity on the line (e.g., a move in the middle)
415 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000416 The path contains a quadratic or cubic.
417 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000418 *The rectangle doesn't complete a cycle.
419 *The final point isn't equal to the first point.
420
421 *These last two conditions we relax if we have a 3-edge path that would
422 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000423
caryclark@google.comf1316942011-07-26 19:54:45 +0000424It's OK if the path has:
425 Several colinear line segments composing a rectangle side.
426 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428The direction takes advantage of the corners found since opposite sides
429must travel in opposite directions.
430
431FIXME: Allow colinear quads and cubics to be treated like lines.
432FIXME: If the API passes fill-only, return true if the filled stroke
433 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000434
435 first,last,next direction state-machine:
436 0x1 is set if the segment is horizontal
437 0x2 is set if the segment is moving to the right or down
438 thus:
439 two directions are opposites iff (dirA ^ dirB) == 0x2
440 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000441
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000443static int rect_make_dir(SkScalar dx, SkScalar dy) {
444 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
445}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000446bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
447 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 int corners = 0;
449 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000450 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700451 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 first.set(0, 0);
453 last.set(0, 0);
454 int firstDirection = 0;
455 int lastDirection = 0;
456 int nextDirection = 0;
457 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700459 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000460 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000461 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700462 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
463 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000465 savePts = pts;
466 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000467 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700468 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 case kLine_Verb: {
470 SkScalar left = last.fX;
471 SkScalar top = last.fY;
472 SkScalar right = pts->fX;
473 SkScalar bottom = pts->fY;
474 ++pts;
475 if (left != right && top != bottom) {
476 return false; // diagonal
477 }
478 if (left == right && top == bottom) {
479 break; // single point on side OK
480 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000481 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 if (0 == corners) {
483 firstDirection = nextDirection;
484 first = last;
485 last = pts[-1];
486 corners = 1;
487 closedOrMoved = false;
488 break;
489 }
490 if (closedOrMoved) {
491 return false; // closed followed by a line
492 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000493 if (autoClose && nextDirection == firstDirection) {
494 break; // colinear with first
495 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 closedOrMoved = autoClose;
497 if (lastDirection != nextDirection) {
498 if (++corners > 4) {
499 return false; // too many direction changes
500 }
501 }
502 last = pts[-1];
503 if (lastDirection == nextDirection) {
504 break; // colinear segment
505 }
506 // Possible values for corners are 2, 3, and 4.
507 // When corners == 3, nextDirection opposes firstDirection.
508 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000509 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
511 if ((directionCycle ^ turn) != nextDirection) {
512 return false; // direction didn't follow cycle
513 }
514 break;
515 }
516 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000517 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 case kCubic_Verb:
519 return false; // quadratic, cubic not allowed
520 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700521 if (allowPartial && !autoClose && firstDirection) {
522 insertClose = true;
523 *currVerb -= 1; // try move again afterwards
524 goto addMissingClose;
525 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000526 last = *pts++;
527 closedOrMoved = true;
528 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000529 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000530 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000531 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000532 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000534 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700535addMissingClose:
536 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 }
538 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000539 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000540 if (!result) {
541 // check if we are just an incomplete rectangle, in which case we can
542 // return true, but not claim to be closed.
543 // e.g.
544 // 3 sided rectangle
545 // 4 sided but the last edge is not long enough to reach the start
546 //
547 SkScalar closeX = first.x() - last.x();
548 SkScalar closeY = first.y() - last.y();
549 if (closeX && closeY) {
550 return false; // we're diagonal, abort (can we ever reach this?)
551 }
552 int closeDirection = rect_make_dir(closeX, closeY);
553 // make sure the close-segment doesn't double-back on itself
554 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
555 result = true;
556 autoClose = false; // we are not closed
557 }
558 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000559 if (savePts) {
560 *ptsPtr = savePts;
561 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000562 if (result && isClosed) {
563 *isClosed = autoClose;
564 }
565 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000566 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 return result;
569}
570
robertphillips4f662e62014-12-29 14:06:51 -0800571bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000572 SkDEBUGCODE(this->validate();)
573 int currVerb = 0;
574 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800575 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800576 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800577 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000578 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800579 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800580 int32_t num = SkToS32(pts - first);
581 if (num) {
582 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800583 } else {
584 // 'pts' isn't updated for open rects
585 *rect = this->getBounds();
586 }
587 }
588 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000589}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000590
caryclark95bc5f32015-04-08 08:34:15 -0700591bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592 SkDEBUGCODE(this->validate();)
593 int currVerb = 0;
594 const SkPoint* pts = fPathRef->points();
595 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000596 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700597 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 return false;
599 }
600 const SkPoint* last = pts;
601 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700602 bool isClosed;
603 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000604 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700605 if (!isClosed) {
606 pts = fPathRef->points() + fPathRef->countPoints();
607 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000608 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 if (testRects[0].contains(testRects[1])) {
610 if (rects) {
611 rects[0] = testRects[0];
612 rects[1] = testRects[1];
613 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (dirs) {
615 dirs[0] = testDirs[0];
616 dirs[1] = testDirs[1];
617 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000618 return true;
619 }
620 if (testRects[1].contains(testRects[0])) {
621 if (rects) {
622 rects[0] = testRects[1];
623 rects[1] = testRects[0];
624 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000625 if (dirs) {
626 dirs[0] = testDirs[1];
627 dirs[1] = testDirs[0];
628 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000629 return true;
630 }
631 }
632 return false;
633}
634
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
caryclarkda707bf2015-11-19 14:47:43 -08001130 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001131 const SkRect& bounds = rrect.getBounds();
1132
Brian Salomon0a241ce2017-12-15 11:31:05 -05001133 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001134 // degenerate(rect) => radii points are collapsing
1135 this->addRect(bounds, dir, (startIndex + 1) / 2);
1136 } else if (rrect.isOval()) {
1137 // degenerate(oval) => line points are collapsing
1138 this->addOval(bounds, dir, startIndex / 2);
1139 } else {
1140 fFirstDirection = this->hasOnlyMoveTos() ?
1141 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1142
1143 SkAutoPathBoundsUpdate apbu(this, bounds);
1144 SkAutoDisableDirectionCheck addc(this);
1145
1146 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1147 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1148 const SkScalar weight = SK_ScalarRoot2Over2;
1149
1150 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1151 const int kVerbs = startsWithConic
1152 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1153 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1154 this->incReserve(kVerbs);
1155
1156 RRectPointIterator rrectIter(rrect, dir, startIndex);
1157 // Corner iterator indices follow the collapsed radii model,
1158 // adjusted such that the start pt is "behind" the radii start pt.
1159 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1160 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1161
1162 this->moveTo(rrectIter.current());
1163 if (startsWithConic) {
1164 for (unsigned i = 0; i < 3; ++i) {
1165 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1166 this->lineTo(rrectIter.next());
1167 }
1168 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1169 // final lineTo handled by close().
1170 } else {
1171 for (unsigned i = 0; i < 4; ++i) {
1172 this->lineTo(rrectIter.next());
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 }
1175 }
1176 this->close();
1177
caryclarkda707bf2015-11-19 14:47:43 -08001178 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001179 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001180
fmalitac08d53e2015-11-17 09:53:29 -08001181 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1182 }
1183
1184 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001185}
1186
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001187bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001188 int count = fPathRef->countVerbs();
1189 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1190 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191 if (*verbs == kLine_Verb ||
1192 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001193 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194 *verbs == kCubic_Verb) {
1195 return false;
1196 }
1197 ++verbs;
1198 }
1199 return true;
1200}
1201
Brian Osmana2318572017-07-10 15:09:26 -04001202bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1203 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001204 if (count < 2) {
1205 return true;
1206 }
Brian Osmana2318572017-07-10 15:09:26 -04001207 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001208 const SkPoint& first = *pts;
1209 for (int index = 1; index < count; ++index) {
1210 if (first != pts[index]) {
1211 return false;
1212 }
1213 }
1214 return true;
1215}
1216
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001217void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1218 Direction dir) {
1219 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001220
humper@google.com75e3ca12013-04-08 21:44:11 +00001221 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001222 return;
1223 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001224
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001225 SkRRect rrect;
1226 rrect.setRectXY(rect, rx, ry);
1227 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001228}
1229
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001231 // legacy start index: 1
1232 this->addOval(oval, dir, 1);
1233}
1234
1235void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001236 assert_known_direction(dir);
1237
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001238 /* If addOval() is called after previous moveTo(),
1239 this path is still marked as an oval. This is used to
1240 fit into WebKit's calling sequences.
1241 We can't simply check isEmpty() in this case, as additional
1242 moveTo() would mark the path non empty.
1243 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001244 bool isOval = hasOnlyMoveTos();
1245 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001246 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001247 } else {
reed026beb52015-06-10 14:23:15 -07001248 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001249 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001250
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001251 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 SkAutoPathBoundsUpdate apbu(this, oval);
1253
fmalitac08d53e2015-11-17 09:53:29 -08001254 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1255 const int kVerbs = 6; // moveTo + 4x conicTo + close
1256 this->incReserve(kVerbs);
1257
1258 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1259 // The corner iterator pts are tracking "behind" the oval/radii pts.
1260 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001261 const SkScalar weight = SK_ScalarRoot2Over2;
1262
fmalitac08d53e2015-11-17 09:53:29 -08001263 this->moveTo(ovalIter.current());
1264 for (unsigned i = 0; i < 4; ++i) {
1265 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001266 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268
fmalitac08d53e2015-11-17 09:53:29 -08001269 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1270
robertphillips@google.com466310d2013-12-03 16:43:54 +00001271 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001272
bsalomon78d58d12016-05-27 09:17:04 -07001273 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001274}
1275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1277 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001278 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 }
1280}
1281
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1283 bool forceMoveTo) {
1284 if (oval.width() < 0 || oval.height() < 0) {
1285 return;
1286 }
1287
reedf90ea012015-01-29 12:03:58 -08001288 if (fPathRef->countVerbs() == 0) {
1289 forceMoveTo = true;
1290 }
1291
1292 SkPoint lonePt;
1293 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1294 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1295 return;
1296 }
1297
reedd5d27d92015-02-09 13:54:43 -08001298 SkVector startV, stopV;
1299 SkRotationDirection dir;
1300 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1301
reed9e779d42015-02-17 11:43:14 -08001302 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001303
1304 // At this point, we know that the arc is not a lone point, but startV == stopV
1305 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1306 // cannot handle it.
1307 if (startV == stopV) {
1308 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1309 SkScalar radiusX = oval.width() / 2;
1310 SkScalar radiusY = oval.height() / 2;
1311 // We cannot use SkScalarSinCos function in the next line because
1312 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1313 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001314 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001315 // make sin(endAngle) to be 0 which will then draw a dot.
1316 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1317 oval.centerY() + radiusY * sk_float_sin(endAngle));
1318 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1319 return;
1320 }
1321
reedd5d27d92015-02-09 13:54:43 -08001322 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001323 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001324 if (count) {
1325 this->incReserve(count * 2 + 1);
1326 const SkPoint& pt = conics[0].fPts[0];
1327 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1328 for (int i = 0; i < count; ++i) {
1329 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1330 }
reed9e779d42015-02-17 11:43:14 -08001331 } else {
1332 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334}
1335
caryclark55d49052016-01-23 05:07:04 -08001336// This converts the SVG arc to conics.
1337// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1338// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1339// See also SVG implementation notes:
1340// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1341// Note that arcSweep bool value is flipped from the original implementation.
1342void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1343 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001344 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001345 SkPoint srcPts[2];
1346 this->getLastPt(&srcPts[0]);
1347 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1348 // joining the endpoints.
1349 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1350 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001351 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001352 return;
1353 }
1354 // If the current point and target point for the arc are identical, it should be treated as a
1355 // zero length path. This ensures continuity in animations.
1356 srcPts[1].set(x, y);
1357 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001358 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001359 return;
1360 }
1361 rx = SkScalarAbs(rx);
1362 ry = SkScalarAbs(ry);
1363 SkVector midPointDistance = srcPts[0] - srcPts[1];
1364 midPointDistance *= 0.5f;
1365
1366 SkMatrix pointTransform;
1367 pointTransform.setRotate(-angle);
1368
1369 SkPoint transformedMidPoint;
1370 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1371 SkScalar squareRx = rx * rx;
1372 SkScalar squareRy = ry * ry;
1373 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1374 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1375
1376 // Check if the radii are big enough to draw the arc, scale radii if not.
1377 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1378 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1379 if (radiiScale > 1) {
1380 radiiScale = SkScalarSqrt(radiiScale);
1381 rx *= radiiScale;
1382 ry *= radiiScale;
1383 }
1384
1385 pointTransform.setScale(1 / rx, 1 / ry);
1386 pointTransform.preRotate(-angle);
1387
1388 SkPoint unitPts[2];
1389 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1390 SkVector delta = unitPts[1] - unitPts[0];
1391
1392 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1393 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1394
1395 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1396 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1397 scaleFactor = -scaleFactor;
1398 }
1399 delta.scale(scaleFactor);
1400 SkPoint centerPoint = unitPts[0] + unitPts[1];
1401 centerPoint *= 0.5f;
1402 centerPoint.offset(-delta.fY, delta.fX);
1403 unitPts[0] -= centerPoint;
1404 unitPts[1] -= centerPoint;
1405 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1406 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1407 SkScalar thetaArc = theta2 - theta1;
1408 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1409 thetaArc += SK_ScalarPI * 2;
1410 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1411 thetaArc -= SK_ScalarPI * 2;
1412 }
1413 pointTransform.setRotate(angle);
1414 pointTransform.preScale(rx, ry);
1415
Cary Clark1acd3c72017-12-08 11:37:01 -05001416#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001417 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001418#else
1419 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1420 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1421#endif
caryclark55d49052016-01-23 05:07:04 -08001422 SkScalar thetaWidth = thetaArc / segments;
1423 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1424 if (!SkScalarIsFinite(t)) {
1425 return;
1426 }
1427 SkScalar startTheta = theta1;
1428 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001429#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1430 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1431 return scalar == SkScalarFloorToScalar(scalar);
1432 };
1433 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1434 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1435 scalar_is_integer(x) && scalar_is_integer(y);
1436#endif
caryclark55d49052016-01-23 05:07:04 -08001437 for (int i = 0; i < segments; ++i) {
1438 SkScalar endTheta = startTheta + thetaWidth;
1439 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1440
1441 unitPts[1].set(cosEndTheta, sinEndTheta);
1442 unitPts[1] += centerPoint;
1443 unitPts[0] = unitPts[1];
1444 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1445 SkPoint mapped[2];
1446 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001447 /*
1448 Computing the arc width introduces rounding errors that cause arcs to start
1449 outside their marks. A round rect may lose convexity as a result. If the input
1450 values are on integers, place the conic on integers as well.
1451 */
1452#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1453 if (expectIntegers) {
1454 SkScalar* mappedScalars = &mapped[0].fX;
1455 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1456 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1457 }
1458 }
1459#endif
caryclark55d49052016-01-23 05:07:04 -08001460 this->conicTo(mapped[0], mapped[1], w);
1461 startTheta = endTheta;
1462 }
1463}
1464
1465void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1466 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1467 SkPoint currentPoint;
1468 this->getLastPt(&currentPoint);
1469 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1470}
1471
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001472void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 if (oval.isEmpty() || 0 == sweepAngle) {
1474 return;
1475 }
1476
1477 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1478
1479 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001480 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1481 // See SkPath::addOval() docs.
1482 SkScalar startOver90 = startAngle / 90.f;
1483 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1484 SkScalar error = startOver90 - startOver90I;
1485 if (SkScalarNearlyEqual(error, 0)) {
1486 // Index 1 is at startAngle == 0.
1487 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1488 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1489 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1490 (unsigned) startIndex);
1491 return;
1492 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 }
bsalomon1978ce22016-05-31 10:42:16 -07001494 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495}
1496
1497/*
1498 Need to handle the case when the angle is sharp, and our computed end-points
1499 for the arc go behind pt1 and/or p2...
1500*/
reedc7789042015-01-29 12:59:11 -08001501void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001502 if (radius == 0) {
1503 this->lineTo(x1, y1);
1504 return;
1505 }
1506
1507 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001508
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 // need to know our prev pt so we can construct tangent vectors
1510 {
1511 SkPoint start;
1512 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001513 // Handle degenerate cases by adding a line to the first point and
1514 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 before.setNormalize(x1 - start.fX, y1 - start.fY);
1516 after.setNormalize(x2 - x1, y2 - y1);
1517 }
reed@google.comabf15c12011-01-18 20:35:51 +00001518
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 SkScalar cosh = SkPoint::DotProduct(before, after);
1520 SkScalar sinh = SkPoint::CrossProduct(before, after);
1521
1522 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001523 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 return;
1525 }
reed@google.comabf15c12011-01-18 20:35:51 +00001526
Mike Reeda99b6ce2017-02-04 11:04:26 -05001527 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528
Mike Reeda99b6ce2017-02-04 11:04:26 -05001529 SkScalar xx = x1 - dist * before.fX;
1530 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001531 after.setLength(dist);
1532 this->lineTo(xx, yy);
1533 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1534 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535}
1536
1537///////////////////////////////////////////////////////////////////////////////
1538
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001539void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 SkMatrix matrix;
1541
1542 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001543 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544}
1545
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001546void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001549 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 SkPoint pts[4];
1551 Verb verb;
1552
Cary Clark9480d822017-10-19 18:01:13 -04001553 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001554 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 while ((verb = iter.next(pts)) != kDone_Verb) {
1556 switch (verb) {
1557 case kMove_Verb:
1558 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001559 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1560 injectMoveToIfNeeded(); // In case last contour is closed
1561 this->lineTo(pts[0]);
1562 } else {
1563 this->moveTo(pts[0]);
1564 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 break;
1566 case kLine_Verb:
1567 proc(matrix, &pts[1], &pts[1], 1);
1568 this->lineTo(pts[1]);
1569 break;
1570 case kQuad_Verb:
1571 proc(matrix, &pts[1], &pts[1], 2);
1572 this->quadTo(pts[1], pts[2]);
1573 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001574 case kConic_Verb:
1575 proc(matrix, &pts[1], &pts[1], 2);
1576 this->conicTo(pts[1], pts[2], iter.conicWeight());
1577 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 case kCubic_Verb:
1579 proc(matrix, &pts[1], &pts[1], 3);
1580 this->cubicTo(pts[1], pts[2], pts[3]);
1581 break;
1582 case kClose_Verb:
1583 this->close();
1584 break;
1585 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001586 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001588 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 }
1590}
1591
1592///////////////////////////////////////////////////////////////////////////////
1593
reed@google.com277c3f82013-05-31 15:17:50 +00001594static int pts_in_verb(unsigned verb) {
1595 static const uint8_t gPtsInVerb[] = {
1596 1, // kMove
1597 1, // kLine
1598 2, // kQuad
1599 2, // kConic
1600 3, // kCubic
1601 0, // kClose
1602 0 // kDone
1603 };
1604
1605 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1606 return gPtsInVerb[verb];
1607}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609// ignore the last point of the 1st contour
1610void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001611 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1612 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 return;
1614 }
caryclark51c56782016-11-07 05:09:28 -08001615 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1616 SkASSERT(verbsEnd[0] == kMove_Verb);
1617 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1618 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619
caryclark51c56782016-11-07 05:09:28 -08001620 while (verbs < verbsEnd) {
1621 uint8_t v = *verbs++;
1622 pts -= pts_in_verb(v);
1623 switch (v) {
1624 case kMove_Verb:
1625 // if the path has multiple contours, stop after reversing the last
1626 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001628 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 break;
1630 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001631 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001633 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001634 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001635 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001637 this->cubicTo(pts[2], pts[1], pts[0]);
1638 break;
1639 case kClose_Verb:
1640 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 break;
1642 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001643 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 break;
1645 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 }
1647}
1648
reed@google.com63d73742012-01-10 15:33:12 +00001649void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001650 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001651
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001652 const SkPoint* pts = src.fPathRef->pointsEnd();
1653 // we will iterator through src's verbs backwards
1654 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1655 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001656 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001657
1658 bool needMove = true;
1659 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 while (verbs < verbsEnd) {
1661 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001662 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001663
1664 if (needMove) {
1665 --pts;
1666 this->moveTo(pts->fX, pts->fY);
1667 needMove = false;
1668 }
1669 pts -= n;
1670 switch (v) {
1671 case kMove_Verb:
1672 if (needClose) {
1673 this->close();
1674 needClose = false;
1675 }
1676 needMove = true;
1677 pts += 1; // so we see the point in "if (needMove)" above
1678 break;
1679 case kLine_Verb:
1680 this->lineTo(pts[0]);
1681 break;
1682 case kQuad_Verb:
1683 this->quadTo(pts[1], pts[0]);
1684 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001685 case kConic_Verb:
1686 this->conicTo(pts[1], pts[0], *--conicWeights);
1687 break;
reed@google.com63d73742012-01-10 15:33:12 +00001688 case kCubic_Verb:
1689 this->cubicTo(pts[2], pts[1], pts[0]);
1690 break;
1691 case kClose_Verb:
1692 needClose = true;
1693 break;
1694 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001695 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001696 }
1697 }
1698}
1699
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700///////////////////////////////////////////////////////////////////////////////
1701
1702void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1703 SkMatrix matrix;
1704
1705 matrix.setTranslate(dx, dy);
1706 this->transform(matrix, dst);
1707}
1708
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1710 int level = 2) {
1711 if (--level >= 0) {
1712 SkPoint tmp[7];
1713
1714 SkChopCubicAtHalf(pts, tmp);
1715 subdivide_cubic_to(path, &tmp[0], level);
1716 subdivide_cubic_to(path, &tmp[3], level);
1717 } else {
1718 path->cubicTo(pts[1], pts[2], pts[3]);
1719 }
1720}
1721
1722void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1723 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001724 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 dst = (SkPath*)this;
1726 }
1727
tomhudson@google.com8d430182011-06-06 19:11:19 +00001728 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 SkPath tmp;
1730 tmp.fFillType = fFillType;
1731
1732 SkPath::Iter iter(*this, false);
1733 SkPoint pts[4];
1734 SkPath::Verb verb;
1735
reed@google.com4a3b7142012-05-16 17:16:46 +00001736 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 switch (verb) {
1738 case kMove_Verb:
1739 tmp.moveTo(pts[0]);
1740 break;
1741 case kLine_Verb:
1742 tmp.lineTo(pts[1]);
1743 break;
1744 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001745 // promote the quad to a conic
1746 tmp.conicTo(pts[1], pts[2],
1747 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001749 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001750 tmp.conicTo(pts[1], pts[2],
1751 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001752 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 case kCubic_Verb:
1754 subdivide_cubic_to(&tmp, pts);
1755 break;
1756 case kClose_Verb:
1757 tmp.close();
1758 break;
1759 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001760 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 break;
1762 }
1763 }
1764
1765 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001766 SkPathRef::Editor ed(&dst->fPathRef);
1767 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001768 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001770 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1771
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001774 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001775 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001777
reed026beb52015-06-10 14:23:15 -07001778 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1779 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001780 } else {
1781 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001782 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1783 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001784 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001785 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1786 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001787 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001788 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001789 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001790 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001791 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 }
1793 }
1794
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 SkDEBUGCODE(dst->validate();)
1796 }
1797}
1798
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799///////////////////////////////////////////////////////////////////////////////
1800///////////////////////////////////////////////////////////////////////////////
1801
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001803 kEmptyContour_SegmentState, // The current contour is empty. We may be
1804 // starting processing or we may have just
1805 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1807 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1808 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809};
1810
1811SkPath::Iter::Iter() {
1812#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001813 fPts = nullptr;
1814 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001817 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818#endif
1819 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001820 fVerbs = nullptr;
1821 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 fNeedClose = false;
1823}
1824
1825SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1826 this->setPath(path, forceClose);
1827}
1828
1829void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 fPts = path.fPathRef->points();
1831 fVerbs = path.fPathRef->verbs();
1832 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001833 fConicWeights = path.fPathRef->conicWeights();
1834 if (fConicWeights) {
1835 fConicWeights -= 1; // begin one behind
1836 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001838 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 fForceClose = SkToU8(forceClose);
1840 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001841 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842}
1843
1844bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001845 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 return false;
1847 }
1848 if (fForceClose) {
1849 return true;
1850 }
1851
1852 const uint8_t* verbs = fVerbs;
1853 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001854
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 if (kMove_Verb == *(verbs - 1)) {
1856 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857 }
1858
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001859 while (verbs > stop) {
1860 // verbs points one beyond the current verb, decrement first.
1861 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 if (kMove_Verb == v) {
1863 break;
1864 }
1865 if (kClose_Verb == v) {
1866 return true;
1867 }
1868 }
1869 return false;
1870}
1871
1872SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001875 // A special case: if both points are NaN, SkPoint::operation== returns
1876 // false, but the iterator expects that they are treated as the same.
1877 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001878 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1879 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001880 return kClose_Verb;
1881 }
1882
reed@google.com9e25dbf2012-05-15 17:05:38 +00001883 pts[0] = fLastPt;
1884 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 fLastPt = fMoveTo;
1886 fCloseLine = true;
1887 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001888 } else {
1889 pts[0] = fMoveTo;
1890 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001892}
1893
reed@google.com9e25dbf2012-05-15 17:05:38 +00001894const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 if (fSegmentState == kAfterMove_SegmentState) {
1896 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1901 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904}
1905
caryclarke8c56662015-07-14 11:19:26 -07001906void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 // We need to step over anything that will not move the current draw point
1908 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001909 const uint8_t* lastMoveVerb = nullptr;
1910 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001911 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 SkPoint lastPt = fLastPt;
1913 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 switch (verb) {
1916 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 // Keep a record of this most recent move
1918 lastMoveVerb = fVerbs;
1919 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001920 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001921 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 fPts++;
1924 break;
1925
1926 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001927 // A close when we are in a segment is always valid except when it
1928 // follows a move which follows a segment.
1929 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 return;
1931 }
1932 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001933 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 break;
1935
1936 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001937 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 if (lastMoveVerb) {
1939 fVerbs = lastMoveVerb;
1940 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001941 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 return;
1943 }
1944 return;
1945 }
1946 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fPts++;
1949 break;
1950
reed@google.com277c3f82013-05-31 15:17:50 +00001951 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001953 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 if (lastMoveVerb) {
1955 fVerbs = lastMoveVerb;
1956 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001957 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 return;
1959 }
1960 return;
1961 }
1962 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001963 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001965 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 break;
1967
1968 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001969 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 if (lastMoveVerb) {
1971 fVerbs = lastMoveVerb;
1972 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001973 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 return;
1975 }
1976 return;
1977 }
1978 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 fPts += 3;
1981 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001982
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001984 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 }
1986 }
1987}
1988
reed@google.com4a3b7142012-05-16 17:16:46 +00001989SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 // Close the curve if requested and if there is some curve to close
1994 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001995 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 return kLine_Verb;
1997 }
1998 fNeedClose = false;
1999 return kClose_Verb;
2000 }
2001 return kDone_Verb;
2002 }
2003
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 // fVerbs is one beyond the current verb, decrement first
2005 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002006 const SkPoint* SK_RESTRICT srcPts = fPts;
2007 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008
2009 switch (verb) {
2010 case kMove_Verb:
2011 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 verb = this->autoClose(pts);
2014 if (verb == kClose_Verb) {
2015 fNeedClose = false;
2016 }
2017 return (Verb)verb;
2018 }
2019 if (fVerbs == fVerbStop) { // might be a trailing moveto
2020 return kDone_Verb;
2021 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002022 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002023 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002025 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002026 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027 fNeedClose = fForceClose;
2028 break;
2029 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002030 pts[0] = this->cons_moveTo();
2031 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 fLastPt = srcPts[0];
2033 fCloseLine = false;
2034 srcPts += 1;
2035 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002036 case kConic_Verb:
2037 fConicWeights += 1;
2038 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002040 pts[0] = this->cons_moveTo();
2041 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 fLastPt = srcPts[1];
2043 srcPts += 2;
2044 break;
2045 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002046 pts[0] = this->cons_moveTo();
2047 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 fLastPt = srcPts[2];
2049 srcPts += 3;
2050 break;
2051 case kClose_Verb:
2052 verb = this->autoClose(pts);
2053 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002054 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 } else {
2056 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002057 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002059 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 break;
2061 }
2062 fPts = srcPts;
2063 return (Verb)verb;
2064}
2065
2066///////////////////////////////////////////////////////////////////////////////
2067
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002069 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070*/
2071
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002072size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2073 SkRect oval;
2074 SkRRect rrect;
2075 bool isCCW;
2076 unsigned start;
2077 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2078 rrect.setOval(oval);
2079 // Convert to rrect start indices.
2080 start *= 2;
2081 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2082 return false;
2083 }
2084 if (!storage) {
2085 // packed header, rrect, start index.
2086 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002087 }
2088
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002089 SkWBuffer buffer(storage);
2090 // Rewrite header's first direction based on rrect direction.
2091 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2092 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2093 packedHeader |= firstDir << kDirection_SerializationShift;
2094 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2095 buffer.write32(packedHeader);
2096 rrect.writeToBuffer(&buffer);
2097 buffer.write32(SkToS32(start));
2098 buffer.padToAlign4();
2099 return buffer.pos();
2100}
2101
2102size_t SkPath::writeToMemory(void* storage) const {
2103 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002104
robertphillips@google.com466310d2013-12-03 16:43:54 +00002105 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002106 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002107 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002108 (fIsVolatile << kIsVolatile_SerializationShift) |
2109 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002110 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2111 return bytes;
2112 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002113
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002114 SkWBuffer buffer(storage);
2115
2116 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2117 if (nullptr == storage) {
2118 // packed header, pathref, start index
2119 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2120 return SkAlign4(byteCount);
2121 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002122 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002123 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002124
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002125 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002126
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002127 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002128 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129}
2130
Mike Reed41a930f2017-07-26 17:33:44 -04002131sk_sp<SkData> SkPath::serialize() const {
2132 size_t size = this->writeToMemory(nullptr);
2133 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2134 this->writeToMemory(data->writable_data());
2135 return data;
2136}
2137
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002138size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002139 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002140
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002141 int32_t packed;
2142 if (!buffer.readS32(&packed)) {
2143 return 0;
2144 }
2145
reed8f086022015-06-11 14:22:19 -07002146 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002147 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2148 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2149 if (version >= kPathPrivTypeEnumVersion) {
2150 SerializationType type =
2151 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2152 switch (type) {
2153 case SerializationType::kRRect: {
2154 Direction rrectDir;
2155 SkRRect rrect;
2156 int32_t start;
2157 switch (dir) {
2158 case SkPathPriv::kCW_FirstDirection:
2159 rrectDir = kCW_Direction;
2160 break;
2161 case SkPathPriv::kCCW_FirstDirection:
2162 rrectDir = kCCW_Direction;
2163 break;
2164 default:
2165 return 0;
2166 }
2167 if (!rrect.readFromBuffer(&buffer)) {
2168 return 0;
2169 }
2170 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2171 return 0;
2172 }
2173 this->reset();
2174 this->addRRect(rrect, rrectDir, SkToUInt(start));
2175 this->setFillType(fillType);
2176 buffer.skipToAlign4();
2177 return buffer.pos();
2178 }
2179 case SerializationType::kGeneral:
2180 // Fall through to general path deserialization
2181 break;
2182 default:
2183 return 0;
2184 }
2185 }
caryclark69006412016-02-17 12:16:27 -08002186 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2187 return 0;
2188 }
mtklein1b249332015-07-07 12:21:21 -07002189
Brian Salomon7072e222017-11-29 15:12:45 -05002190 // These are written into the serialized data but we no longer use them in the deserialized
2191 // path. If convexity is corrupted it may cause the GPU backend to make incorrect
2192 // rendering choices, possibly crashing. We set them to unknown so that they'll be recomputed if
2193 // requested.
2194 fConvexity = kUnknown_Convexity;
2195 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2196
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002197 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002198 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002199 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002200 if (!pathRef) {
2201 return 0;
2202 }
2203
2204 fPathRef.reset(pathRef);
2205 SkDEBUGCODE(this->validate();)
2206 buffer.skipToAlign4();
ajumaf8aec582016-01-13 13:46:31 -08002207 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208}
2209
2210///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002211
Ben Wagner4d1955c2017-03-10 13:08:15 -05002212#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002213#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002214#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002215
reed@google.com51bbe752013-01-17 22:07:50 +00002216static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002217 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002218 str->append(label);
2219 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002220
reed@google.com51bbe752013-01-17 22:07:50 +00002221 const SkScalar* values = &pts[0].fX;
2222 count *= 2;
2223
2224 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002225 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002226 if (i < count - 1) {
2227 str->append(", ");
2228 }
2229 }
Mike Reed405b9d22018-01-09 15:11:08 -05002230 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002231 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002232 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002233 }
caryclark08fa28c2014-10-23 13:08:56 -07002234 str->append(");");
reede05fed02014-12-15 07:59:53 -08002235 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002236 str->append(" // ");
2237 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002238 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002239 if (i < count - 1) {
2240 str->append(", ");
2241 }
2242 }
2243 if (conicWeight >= 0) {
2244 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002245 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002246 }
2247 }
2248 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002249}
2250
caryclarke9562592014-09-15 09:26:09 -07002251void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002252 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002253 Iter iter(*this, forceClose);
2254 SkPoint pts[4];
2255 Verb verb;
2256
reed@google.com51bbe752013-01-17 22:07:50 +00002257 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002258 char const * const gFillTypeStrs[] = {
2259 "Winding",
2260 "EvenOdd",
2261 "InverseWinding",
2262 "InverseEvenOdd",
2263 };
2264 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2265 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002266 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002267 switch (verb) {
2268 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002269 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002270 break;
2271 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002272 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002273 break;
2274 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002275 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002276 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002277 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002278 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002279 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002280 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002281 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002282 break;
2283 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002284 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002285 break;
2286 default:
2287 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2288 verb = kDone_Verb; // stop the loop
2289 break;
2290 }
caryclark1049f122015-04-20 08:31:59 -07002291 if (!wStream && builder.size()) {
2292 SkDebugf("%s", builder.c_str());
2293 builder.reset();
2294 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002295 }
caryclark66a5d8b2014-06-24 08:30:15 -07002296 if (wStream) {
2297 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002299}
2300
reed@android.come522ca52009-11-23 20:10:41 +00002301void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002302 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002303}
2304
2305void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002306 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002307}
2308
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002309
Cary Clark0461e9f2017-08-25 15:13:38 -04002310bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002311 if ((fFillType & ~3) != 0) {
2312 return false;
2313 }
reed@google.comabf15c12011-01-18 20:35:51 +00002314
djsollen@google.com077348c2012-10-22 20:23:32 +00002315#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002316 if (!fBoundsIsDirty) {
2317 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002318
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002319 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002320 if (SkToBool(fIsFinite) != isFinite) {
2321 return false;
2322 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002323
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002324 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002325 // if we're empty, fBounds may be empty but translated, so we can't
2326 // necessarily compare to bounds directly
2327 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2328 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002329 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2330 return false;
2331 }
reed@android.come522ca52009-11-23 20:10:41 +00002332 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002333 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002334 if (!fBounds.isEmpty()) {
2335 return false;
2336 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002337 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002338 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002339 if (!fBounds.contains(bounds)) {
2340 return false;
2341 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002342 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002343 }
reed@android.come522ca52009-11-23 20:10:41 +00002344 }
2345 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002346#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002347 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002348}
reed@android.come522ca52009-11-23 20:10:41 +00002349
reed@google.com04863fa2011-05-15 04:08:24 +00002350///////////////////////////////////////////////////////////////////////////////
2351
reed@google.com0b7b9822011-05-16 12:29:27 +00002352static int sign(SkScalar x) { return x < 0; }
2353#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002354
robertphillipsc506e302014-09-16 09:43:31 -07002355enum DirChange {
2356 kLeft_DirChange,
2357 kRight_DirChange,
2358 kStraight_DirChange,
2359 kBackwards_DirChange,
2360
2361 kInvalid_DirChange
2362};
2363
2364
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002365static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002366 // The error epsilon was empirically derived; worse case round rects
2367 // with a mid point outset by 2x float epsilon in tests had an error
2368 // of 12.
2369 const int epsilon = 16;
2370 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2371 return false;
2372 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002373 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002374 int aBits = SkFloatAs2sCompliment(compA);
2375 int bBits = SkFloatAs2sCompliment(compB);
2376 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002377}
2378
caryclarkb4216502015-03-02 13:02:34 -08002379static bool approximately_zero_when_compared_to(double x, double y) {
2380 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002381}
2382
caryclarkb4216502015-03-02 13:02:34 -08002383
reed@google.com04863fa2011-05-15 04:08:24 +00002384// only valid for a single contour
2385struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002386 Convexicator()
2387 : fPtCount(0)
2388 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002389 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002390 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002391 , fIsCurve(false)
2392 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002393 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002394 // warnings
djsollen2f124632016-04-29 13:53:05 -07002395 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002396 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002397 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002398 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002399 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002400
2401 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002402 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002403 }
2404
2405 SkPath::Convexity getConvexity() const { return fConvexity; }
2406
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002407 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002408 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409
reed@google.com04863fa2011-05-15 04:08:24 +00002410 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002411 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002412 return;
2413 }
2414
2415 if (0 == fPtCount) {
2416 fCurrPt = pt;
2417 ++fPtCount;
2418 } else {
2419 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002420 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002421 if (!SkScalarIsFinite(lengthSqd)) {
2422 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002423 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002424 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002425 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002426 fCurrPt = pt;
2427 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002428 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002429 } else {
2430 SkASSERT(fPtCount > 2);
2431 this->addVec(vec);
2432 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002433
reed@google.com85b6e392011-05-15 20:25:17 +00002434 int sx = sign(vec.fX);
2435 int sy = sign(vec.fY);
2436 fDx += (sx != fSx);
2437 fDy += (sy != fSy);
2438 fSx = sx;
2439 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002440
reed@google.com85b6e392011-05-15 20:25:17 +00002441 if (fDx > 3 || fDy > 3) {
2442 fConvexity = SkPath::kConcave_Convexity;
2443 }
reed@google.com04863fa2011-05-15 04:08:24 +00002444 }
2445 }
2446 }
2447
2448 void close() {
2449 if (fPtCount > 2) {
2450 this->addVec(fFirstVec);
2451 }
2452 }
2453
caryclarkb4216502015-03-02 13:02:34 -08002454 DirChange directionChange(const SkVector& curVec) {
2455 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2456
2457 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2458 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2459 largest = SkTMax(largest, -smallest);
2460
2461 if (!almost_equal(largest, largest + cross)) {
2462 int sign = SkScalarSignAsInt(cross);
2463 if (sign) {
2464 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2465 }
2466 }
2467
2468 if (cross) {
2469 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2470 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2471 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2472 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2473 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2474 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2475 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2476 if (sign) {
2477 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2478 }
2479 }
2480 }
2481
Cary Clarkdf429f32017-11-08 11:44:31 -05002482 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2483 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2484 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2485 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002486 fLastVec.dot(curVec) < 0.0f) {
2487 return kBackwards_DirChange;
2488 }
2489
2490 return kStraight_DirChange;
2491 }
2492
Cary Clarkc8323aa2017-08-25 08:04:43 -04002493 bool hasBackwards() const {
2494 return fBackwards;
2495 }
caryclarkb4216502015-03-02 13:02:34 -08002496
caryclarkd3d1a982014-12-08 04:57:38 -08002497 bool isFinite() const {
2498 return fIsFinite;
2499 }
2500
caryclark5ccef572015-03-02 10:07:56 -08002501 void setCurve(bool isCurve) {
2502 fIsCurve = isCurve;
2503 }
2504
reed@google.com04863fa2011-05-15 04:08:24 +00002505private:
2506 void addVec(const SkVector& vec) {
2507 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002508 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002509 switch (dir) {
2510 case kLeft_DirChange: // fall through
2511 case kRight_DirChange:
2512 if (kInvalid_DirChange == fExpectedDir) {
2513 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002514 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2515 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002516 } else if (dir != fExpectedDir) {
2517 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002518 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002519 }
2520 fLastVec = vec;
2521 break;
2522 case kStraight_DirChange:
2523 break;
2524 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002525 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002526 // If any of the subsequent dir is non-backward, it'll be concave.
2527 // Otherwise, it's still convex.
2528 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002529 }
robertphillipsc506e302014-09-16 09:43:31 -07002530 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002531 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002532 break;
2533 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002534 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002535 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002536 }
2537 }
2538
caryclarkb4216502015-03-02 13:02:34 -08002539 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002540 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002541 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002542 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2543 // value with the current vec is deemed to be of a significant value.
2544 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002545 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002546 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002547 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002548 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002549 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002550 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002551 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002552 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002553};
2554
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002555SkPath::Convexity SkPath::internalGetConvexity() const {
2556 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002557 SkPoint pts[4];
2558 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002559 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002560
2561 int contourCount = 0;
2562 int count;
2563 Convexicator state;
2564
caryclarkd3d1a982014-12-08 04:57:38 -08002565 if (!isFinite()) {
2566 return kUnknown_Convexity;
2567 }
Brian Osman205a1262017-09-18 13:13:48 +00002568 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002569 switch (verb) {
2570 case kMove_Verb:
2571 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002572 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002573 return kConcave_Convexity;
2574 }
2575 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002576 // fall through
2577 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002578 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002579 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002580 break;
caryclark5ccef572015-03-02 10:07:56 -08002581 case kQuad_Verb:
2582 // fall through
2583 case kConic_Verb:
2584 // fall through
2585 case kCubic_Verb:
2586 count = 2 + (kCubic_Verb == verb);
2587 // As an additional enhancement, this could set curve true only
2588 // if the curve is nonlinear
2589 state.setCurve(true);
2590 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002591 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002592 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002593 state.close();
2594 count = 0;
2595 break;
2596 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002597 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002598 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002599 return kConcave_Convexity;
2600 }
2601
2602 for (int i = 1; i <= count; i++) {
2603 state.addPt(pts[i]);
2604 }
2605 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002606 if (!state.isFinite()) {
2607 return kUnknown_Convexity;
2608 }
reed@google.com04863fa2011-05-15 04:08:24 +00002609 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002610 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002611 return kConcave_Convexity;
2612 }
2613 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002614 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002615 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002616 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2617 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2618 fConvexity = Convexity::kConcave_Convexity;
2619 } else {
2620 fFirstDirection = state.getFirstDirection();
2621 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002622 }
2623 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002624}
reed@google.com69a99432012-01-10 18:00:10 +00002625
2626///////////////////////////////////////////////////////////////////////////////
2627
2628class ContourIter {
2629public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002630 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002631
2632 bool done() const { return fDone; }
2633 // if !done() then these may be called
2634 int count() const { return fCurrPtCount; }
2635 const SkPoint* pts() const { return fCurrPt; }
2636 void next();
2637
2638private:
2639 int fCurrPtCount;
2640 const SkPoint* fCurrPt;
2641 const uint8_t* fCurrVerb;
2642 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002643 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002644 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002645 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002646};
2647
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002648ContourIter::ContourIter(const SkPathRef& pathRef) {
2649 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002650 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002651 fCurrPt = pathRef.points();
2652 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002653 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002654 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002655 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002656 this->next();
2657}
2658
2659void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002660 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002661 fDone = true;
2662 }
2663 if (fDone) {
2664 return;
2665 }
2666
2667 // skip pts of prev contour
2668 fCurrPt += fCurrPtCount;
2669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002670 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002671 int ptCount = 1; // moveTo
2672 const uint8_t* verbs = fCurrVerb;
2673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002674 for (--verbs; verbs > fStopVerbs; --verbs) {
2675 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002676 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002677 goto CONTOUR_END;
2678 case SkPath::kLine_Verb:
2679 ptCount += 1;
2680 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002681 case SkPath::kConic_Verb:
2682 fCurrConicWeight += 1;
2683 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002684 case SkPath::kQuad_Verb:
2685 ptCount += 2;
2686 break;
2687 case SkPath::kCubic_Verb:
2688 ptCount += 3;
2689 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002690 case SkPath::kClose_Verb:
2691 break;
2692 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002693 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002694 break;
2695 }
2696 }
2697CONTOUR_END:
2698 fCurrPtCount = ptCount;
2699 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002700 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002701}
2702
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002703// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002704static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002705 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2706 // We may get 0 when the above subtracts underflow. We expect this to be
2707 // very rare and lazily promote to double.
2708 if (0 == cross) {
2709 double p0x = SkScalarToDouble(p0.fX);
2710 double p0y = SkScalarToDouble(p0.fY);
2711
2712 double p1x = SkScalarToDouble(p1.fX);
2713 double p1y = SkScalarToDouble(p1.fY);
2714
2715 double p2x = SkScalarToDouble(p2.fX);
2716 double p2y = SkScalarToDouble(p2.fY);
2717
2718 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2719 (p1y - p0y) * (p2x - p0x));
2720
2721 }
2722 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002723}
2724
reed@google.comc1ea60a2012-01-31 15:15:36 +00002725// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002726static int find_max_y(const SkPoint pts[], int count) {
2727 SkASSERT(count > 0);
2728 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002729 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002730 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002731 SkScalar y = pts[i].fY;
2732 if (y > max) {
2733 max = y;
2734 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002735 }
2736 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002737 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002738}
2739
reed@google.comcabaf1d2012-01-11 21:03:05 +00002740static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2741 int i = index;
2742 for (;;) {
2743 i = (i + inc) % n;
2744 if (i == index) { // we wrapped around, so abort
2745 break;
2746 }
2747 if (pts[index] != pts[i]) { // found a different point, success!
2748 break;
2749 }
2750 }
2751 return i;
2752}
2753
reed@google.comc1ea60a2012-01-31 15:15:36 +00002754/**
2755 * Starting at index, and moving forward (incrementing), find the xmin and
2756 * xmax of the contiguous points that have the same Y.
2757 */
2758static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2759 int* maxIndexPtr) {
2760 const SkScalar y = pts[index].fY;
2761 SkScalar min = pts[index].fX;
2762 SkScalar max = min;
2763 int minIndex = index;
2764 int maxIndex = index;
2765 for (int i = index + 1; i < n; ++i) {
2766 if (pts[i].fY != y) {
2767 break;
2768 }
2769 SkScalar x = pts[i].fX;
2770 if (x < min) {
2771 min = x;
2772 minIndex = i;
2773 } else if (x > max) {
2774 max = x;
2775 maxIndex = i;
2776 }
2777 }
2778 *maxIndexPtr = maxIndex;
2779 return minIndex;
2780}
2781
reed026beb52015-06-10 14:23:15 -07002782static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2783 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002784}
2785
reed@google.comac8543f2012-01-30 20:51:25 +00002786/*
2787 * We loop through all contours, and keep the computed cross-product of the
2788 * contour that contained the global y-max. If we just look at the first
2789 * contour, we may find one that is wound the opposite way (correctly) since
2790 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2791 * that is outer most (or at least has the global y-max) before we can consider
2792 * its cross product.
2793 */
reed026beb52015-06-10 14:23:15 -07002794bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002795 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2796 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002797 return true;
2798 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002799
2800 // don't want to pay the cost for computing this if it
2801 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002802 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2803 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002804 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002805 return false;
2806 }
reed@google.com69a99432012-01-10 18:00:10 +00002807
reed026beb52015-06-10 14:23:15 -07002808 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002809
reed@google.comac8543f2012-01-30 20:51:25 +00002810 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002811 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002812 SkScalar ymaxCross = 0;
2813
reed@google.com69a99432012-01-10 18:00:10 +00002814 for (; !iter.done(); iter.next()) {
2815 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002816 if (n < 3) {
2817 continue;
2818 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002819
reed@google.comcabaf1d2012-01-11 21:03:05 +00002820 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002821 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002822 int index = find_max_y(pts, n);
2823 if (pts[index].fY < ymax) {
2824 continue;
2825 }
2826
2827 // If there is more than 1 distinct point at the y-max, we take the
2828 // x-min and x-max of them and just subtract to compute the dir.
2829 if (pts[(index + 1) % n].fY == pts[index].fY) {
2830 int maxIndex;
2831 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2832 if (minIndex == maxIndex) {
2833 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002834 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002835 SkASSERT(pts[minIndex].fY == pts[index].fY);
2836 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2837 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2838 // we just subtract the indices, and let that auto-convert to
2839 // SkScalar, since we just want - or + to signal the direction.
2840 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002841 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002842 TRY_CROSSPROD:
2843 // Find a next and prev index to use for the cross-product test,
2844 // but we try to find pts that form non-zero vectors from pts[index]
2845 //
2846 // Its possible that we can't find two non-degenerate vectors, so
2847 // we have to guard our search (e.g. all the pts could be in the
2848 // same place).
2849
2850 // we pass n - 1 instead of -1 so we don't foul up % operator by
2851 // passing it a negative LH argument.
2852 int prev = find_diff_pt(pts, index, n, n - 1);
2853 if (prev == index) {
2854 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002855 continue;
2856 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002857 int next = find_diff_pt(pts, index, n, 1);
2858 SkASSERT(next != index);
2859 cross = cross_prod(pts[prev], pts[index], pts[next]);
2860 // if we get a zero and the points are horizontal, then we look at the spread in
2861 // x-direction. We really should continue to walk away from the degeneracy until
2862 // there is a divergence.
2863 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2864 // construct the subtract so we get the correct Direction below
2865 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002866 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002867 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002868
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002869 if (cross) {
2870 // record our best guess so far
2871 ymax = pts[index].fY;
2872 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002873 }
2874 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002875 if (ymaxCross) {
2876 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002877 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002878 return true;
2879 } else {
2880 return false;
2881 }
reed@google.comac8543f2012-01-30 20:51:25 +00002882}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002883
2884///////////////////////////////////////////////////////////////////////////////
2885
caryclark9aacd902015-12-14 08:38:09 -08002886static bool between(SkScalar a, SkScalar b, SkScalar c) {
2887 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2888 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2889 return (a - b) * (c - b) <= 0;
2890}
2891
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2893 SkScalar t) {
2894 SkScalar A = c3 + 3*(c1 - c2) - c0;
2895 SkScalar B = 3*(c2 - c1 - c1 + c0);
2896 SkScalar C = 3*(c1 - c0);
2897 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002898 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002899}
2900
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901template <size_t N> static void find_minmax(const SkPoint pts[],
2902 SkScalar* minPtr, SkScalar* maxPtr) {
2903 SkScalar min, max;
2904 min = max = pts[0].fX;
2905 for (size_t i = 1; i < N; ++i) {
2906 min = SkMinScalar(min, pts[i].fX);
2907 max = SkMaxScalar(max, pts[i].fX);
2908 }
2909 *minPtr = min;
2910 *maxPtr = max;
2911}
2912
caryclark9cb5d752015-12-18 04:35:24 -08002913static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2914 if (start.fY == end.fY) {
2915 return between(start.fX, x, end.fX) && x != end.fX;
2916 } else {
2917 return x == start.fX && y == start.fY;
2918 }
2919}
2920
caryclark9aacd902015-12-14 08:38:09 -08002921static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002922 SkScalar y0 = pts[0].fY;
2923 SkScalar y3 = pts[3].fY;
2924
2925 int dir = 1;
2926 if (y0 > y3) {
2927 SkTSwap(y0, y3);
2928 dir = -1;
2929 }
2930 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002931 return 0;
2932 }
caryclark9cb5d752015-12-18 04:35:24 -08002933 if (checkOnCurve(x, y, pts[0], pts[3])) {
2934 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002935 return 0;
2936 }
caryclark9cb5d752015-12-18 04:35:24 -08002937 if (y == y3) {
2938 return 0;
2939 }
2940
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 // quickreject or quickaccept
2942 SkScalar min, max;
2943 find_minmax<4>(pts, &min, &max);
2944 if (x < min) {
2945 return 0;
2946 }
2947 if (x > max) {
2948 return dir;
2949 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002950
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002952 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002953 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002954 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002955 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002956 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002957 if (SkScalarNearlyEqual(xt, x)) {
2958 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2959 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002960 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002961 }
2962 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 return xt < x ? dir : 0;
2964}
2965
caryclark9aacd902015-12-14 08:38:09 -08002966static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 SkPoint dst[10];
2968 int n = SkChopCubicAtYExtrema(pts, dst);
2969 int w = 0;
2970 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002971 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 }
2973 return w;
2974}
2975
caryclark9aacd902015-12-14 08:38:09 -08002976static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2977 SkASSERT(src);
2978 SkASSERT(t >= 0 && t <= 1);
2979 SkScalar src2w = src[2] * w;
2980 SkScalar C = src[0];
2981 SkScalar A = src[4] - 2 * src2w + C;
2982 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002983 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002984}
2985
2986
2987static double conic_eval_denominator(SkScalar w, SkScalar t) {
2988 SkScalar B = 2 * (w - 1);
2989 SkScalar C = 1;
2990 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002991 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002992}
2993
2994static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2995 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 SkScalar y0 = pts[0].fY;
2997 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002998
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 int dir = 1;
3000 if (y0 > y2) {
3001 SkTSwap(y0, y2);
3002 dir = -1;
3003 }
caryclark9aacd902015-12-14 08:38:09 -08003004 if (y < y0 || y > y2) {
3005 return 0;
3006 }
caryclark9cb5d752015-12-18 04:35:24 -08003007 if (checkOnCurve(x, y, pts[0], pts[2])) {
3008 *onCurveCount += 1;
3009 return 0;
3010 }
caryclark9aacd902015-12-14 08:38:09 -08003011 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 return 0;
3013 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003014
caryclark9aacd902015-12-14 08:38:09 -08003015 SkScalar roots[2];
3016 SkScalar A = pts[2].fY;
3017 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3018 SkScalar C = pts[0].fY;
3019 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3020 B -= C; // B = b*w - w * yCept + yCept - a
3021 C -= y;
3022 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3023 SkASSERT(n <= 1);
3024 SkScalar xt;
3025 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003026 // zero roots are returned only when y0 == y
3027 // Need [0] if dir == 1
3028 // and [2] if dir == -1
3029 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003030 } else {
3031 SkScalar t = roots[0];
3032 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3033 }
3034 if (SkScalarNearlyEqual(xt, x)) {
3035 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3036 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003037 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003038 }
3039 }
3040 return xt < x ? dir : 0;
3041}
3042
3043static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3044 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3045 if (y0 == y1) {
3046 return true;
3047 }
3048 if (y0 < y1) {
3049 return y1 <= y2;
3050 } else {
3051 return y1 >= y2;
3052 }
3053}
3054
3055static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3056 int* onCurveCount) {
3057 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003058 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003059 // If the data points are very large, the conic may not be monotonic but may also
3060 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003061 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3062 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3063 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003064 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3065 }
3066 return w;
3067}
3068
3069static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3070 SkScalar y0 = pts[0].fY;
3071 SkScalar y2 = pts[2].fY;
3072
3073 int dir = 1;
3074 if (y0 > y2) {
3075 SkTSwap(y0, y2);
3076 dir = -1;
3077 }
3078 if (y < y0 || y > y2) {
3079 return 0;
3080 }
caryclark9cb5d752015-12-18 04:35:24 -08003081 if (checkOnCurve(x, y, pts[0], pts[2])) {
3082 *onCurveCount += 1;
3083 return 0;
3084 }
caryclark9aacd902015-12-14 08:38:09 -08003085 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003086 return 0;
3087 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003088 // bounds check on X (not required. is it faster?)
3089#if 0
3090 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3091 return 0;
3092 }
3093#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003094
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003095 SkScalar roots[2];
3096 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3097 2 * (pts[1].fY - pts[0].fY),
3098 pts[0].fY - y,
3099 roots);
3100 SkASSERT(n <= 1);
3101 SkScalar xt;
3102 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003103 // zero roots are returned only when y0 == y
3104 // Need [0] if dir == 1
3105 // and [2] if dir == -1
3106 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003107 } else {
3108 SkScalar t = roots[0];
3109 SkScalar C = pts[0].fX;
3110 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3111 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003112 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003113 }
caryclark9aacd902015-12-14 08:38:09 -08003114 if (SkScalarNearlyEqual(xt, x)) {
3115 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3116 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003117 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003118 }
3119 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003120 return xt < x ? dir : 0;
3121}
3122
caryclark9aacd902015-12-14 08:38:09 -08003123static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003124 SkPoint dst[5];
3125 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003126
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003127 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3128 n = SkChopQuadAtYExtrema(pts, dst);
3129 pts = dst;
3130 }
caryclark9aacd902015-12-14 08:38:09 -08003131 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003132 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003133 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003134 }
3135 return w;
3136}
3137
caryclark9aacd902015-12-14 08:38:09 -08003138static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003139 SkScalar x0 = pts[0].fX;
3140 SkScalar y0 = pts[0].fY;
3141 SkScalar x1 = pts[1].fX;
3142 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003143
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003144 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003145
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003146 int dir = 1;
3147 if (y0 > y1) {
3148 SkTSwap(y0, y1);
3149 dir = -1;
3150 }
caryclark9aacd902015-12-14 08:38:09 -08003151 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003152 return 0;
3153 }
caryclark9cb5d752015-12-18 04:35:24 -08003154 if (checkOnCurve(x, y, pts[0], pts[1])) {
3155 *onCurveCount += 1;
3156 return 0;
3157 }
3158 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003159 return 0;
3160 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003161 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003162
caryclark9aacd902015-12-14 08:38:09 -08003163 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003164 // zero cross means the point is on the line, and since the case where
3165 // y of the query point is at the end point is handled above, we can be
3166 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003167 if (x != x1 || y != pts[1].fY) {
3168 *onCurveCount += 1;
3169 }
caryclark9aacd902015-12-14 08:38:09 -08003170 dir = 0;
3171 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172 dir = 0;
3173 }
3174 return dir;
3175}
3176
caryclark9aacd902015-12-14 08:38:09 -08003177static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3178 SkTDArray<SkVector>* tangents) {
3179 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3180 && !between(pts[2].fY, y, pts[3].fY)) {
3181 return;
3182 }
3183 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3184 && !between(pts[2].fX, x, pts[3].fX)) {
3185 return;
3186 }
3187 SkPoint dst[10];
3188 int n = SkChopCubicAtYExtrema(pts, dst);
3189 for (int i = 0; i <= n; ++i) {
3190 SkPoint* c = &dst[i * 3];
3191 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003192 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003193 continue;
mbarbella276e6332016-05-31 14:44:01 -07003194 }
caryclark9aacd902015-12-14 08:38:09 -08003195 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3196 if (!SkScalarNearlyEqual(x, xt)) {
3197 continue;
3198 }
3199 SkVector tangent;
3200 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3201 tangents->push(tangent);
3202 }
3203}
3204
3205static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3206 SkTDArray<SkVector>* tangents) {
3207 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3208 return;
3209 }
3210 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3211 return;
3212 }
3213 SkScalar roots[2];
3214 SkScalar A = pts[2].fY;
3215 SkScalar B = pts[1].fY * w - y * w + y;
3216 SkScalar C = pts[0].fY;
3217 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3218 B -= C; // B = b*w - w * yCept + yCept - a
3219 C -= y;
3220 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3221 for (int index = 0; index < n; ++index) {
3222 SkScalar t = roots[index];
3223 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3224 if (!SkScalarNearlyEqual(x, xt)) {
3225 continue;
3226 }
3227 SkConic conic(pts, w);
3228 tangents->push(conic.evalTangentAt(t));
3229 }
3230}
3231
3232static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3233 SkTDArray<SkVector>* tangents) {
3234 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3235 return;
3236 }
3237 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3238 return;
3239 }
3240 SkScalar roots[2];
3241 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3242 2 * (pts[1].fY - pts[0].fY),
3243 pts[0].fY - y,
3244 roots);
3245 for (int index = 0; index < n; ++index) {
3246 SkScalar t = roots[index];
3247 SkScalar C = pts[0].fX;
3248 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3249 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003250 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003251 if (!SkScalarNearlyEqual(x, xt)) {
3252 continue;
3253 }
3254 tangents->push(SkEvalQuadTangentAt(pts, t));
3255 }
3256}
3257
3258static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3259 SkTDArray<SkVector>* tangents) {
3260 SkScalar y0 = pts[0].fY;
3261 SkScalar y1 = pts[1].fY;
3262 if (!between(y0, y, y1)) {
3263 return;
3264 }
3265 SkScalar x0 = pts[0].fX;
3266 SkScalar x1 = pts[1].fX;
3267 if (!between(x0, x, x1)) {
3268 return;
3269 }
3270 SkScalar dx = x1 - x0;
3271 SkScalar dy = y1 - y0;
3272 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3273 return;
3274 }
3275 SkVector v;
3276 v.set(dx, dy);
3277 tangents->push(v);
3278}
3279
reed@google.com4db592c2013-10-30 17:39:43 +00003280static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3281 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3282}
3283
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003284bool SkPath::contains(SkScalar x, SkScalar y) const {
3285 bool isInverse = this->isInverseFillType();
3286 if (this->isEmpty()) {
3287 return isInverse;
3288 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003289
reed@google.com4db592c2013-10-30 17:39:43 +00003290 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003291 return isInverse;
3292 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003293
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003294 SkPath::Iter iter(*this, true);
3295 bool done = false;
3296 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003297 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003298 do {
3299 SkPoint pts[4];
3300 switch (iter.next(pts, false)) {
3301 case SkPath::kMove_Verb:
3302 case SkPath::kClose_Verb:
3303 break;
3304 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003305 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003306 break;
3307 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003308 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003309 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003310 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003311 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003312 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003313 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003314 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003315 break;
3316 case SkPath::kDone_Verb:
3317 done = true;
3318 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003319 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003320 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003321 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3322 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3323 if (evenOddFill) {
3324 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003325 }
caryclark9aacd902015-12-14 08:38:09 -08003326 if (w) {
3327 return !isInverse;
3328 }
3329 if (onCurveCount <= 1) {
3330 return SkToBool(onCurveCount) ^ isInverse;
3331 }
3332 if ((onCurveCount & 1) || evenOddFill) {
3333 return SkToBool(onCurveCount & 1) ^ isInverse;
3334 }
halcanary9d524f22016-03-29 09:03:52 -07003335 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003336 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3337 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003338 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003339 SkTDArray<SkVector> tangents;
3340 do {
3341 SkPoint pts[4];
3342 int oldCount = tangents.count();
3343 switch (iter.next(pts, false)) {
3344 case SkPath::kMove_Verb:
3345 case SkPath::kClose_Verb:
3346 break;
3347 case SkPath::kLine_Verb:
3348 tangent_line(pts, x, y, &tangents);
3349 break;
3350 case SkPath::kQuad_Verb:
3351 tangent_quad(pts, x, y, &tangents);
3352 break;
3353 case SkPath::kConic_Verb:
3354 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3355 break;
3356 case SkPath::kCubic_Verb:
3357 tangent_cubic(pts, x, y, &tangents);
3358 break;
3359 case SkPath::kDone_Verb:
3360 done = true;
3361 break;
3362 }
3363 if (tangents.count() > oldCount) {
3364 int last = tangents.count() - 1;
3365 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003366 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003367 tangents.remove(last);
3368 } else {
3369 for (int index = 0; index < last; ++index) {
3370 const SkVector& test = tangents[index];
3371 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003372 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3373 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003374 tangents.remove(last);
3375 tangents.removeShuffle(index);
3376 break;
3377 }
3378 }
3379 }
3380 }
3381 } while (!done);
3382 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003383}
fmalitaaa0df4e2015-12-01 09:13:23 -08003384
3385int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3386 SkScalar w, SkPoint pts[], int pow2) {
3387 const SkConic conic(p0, p1, p2, w);
3388 return conic.chopIntoQuadsPOW2(pts, pow2);
3389}
bsalomonedc743a2016-06-01 09:42:31 -07003390
3391bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3392 unsigned* start) {
3393 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3394 return false;
3395 }
3396 SkPath::RawIter iter(path);
3397 SkPoint verbPts[4];
3398 SkPath::Verb v;
3399 SkPoint rectPts[5];
3400 int rectPtCnt = 0;
3401 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3402 switch (v) {
3403 case SkPath::kMove_Verb:
3404 if (0 != rectPtCnt) {
3405 return false;
3406 }
3407 rectPts[0] = verbPts[0];
3408 ++rectPtCnt;
3409 break;
3410 case SkPath::kLine_Verb:
3411 if (5 == rectPtCnt) {
3412 return false;
3413 }
3414 rectPts[rectPtCnt] = verbPts[1];
3415 ++rectPtCnt;
3416 break;
3417 case SkPath::kClose_Verb:
3418 if (4 == rectPtCnt) {
3419 rectPts[4] = rectPts[0];
3420 rectPtCnt = 5;
3421 }
3422 break;
3423 default:
3424 return false;
3425 }
3426 }
3427 if (rectPtCnt < 5) {
3428 return false;
3429 }
3430 if (rectPts[0] != rectPts[4]) {
3431 return false;
3432 }
bsalomon057ae8a2016-07-24 05:37:26 -07003433 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3434 // and pts 1 and 2 the opposite vertical or horizontal edge).
3435 bool vec03IsVertical;
3436 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3437 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3438 // Make sure it has non-zero width and height
3439 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003440 return false;
3441 }
bsalomon057ae8a2016-07-24 05:37:26 -07003442 vec03IsVertical = true;
3443 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3444 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3445 // Make sure it has non-zero width and height
3446 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3447 return false;
3448 }
3449 vec03IsVertical = false;
3450 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003451 return false;
3452 }
bsalomon057ae8a2016-07-24 05:37:26 -07003453 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3454 // set if it is on the bottom edge.
3455 unsigned sortFlags =
3456 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3457 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3458 switch (sortFlags) {
3459 case 0b00:
3460 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3461 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3462 *start = 0;
3463 break;
3464 case 0b01:
3465 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3466 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3467 *start = 1;
3468 break;
3469 case 0b10:
3470 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3471 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3472 *start = 3;
3473 break;
3474 case 0b11:
3475 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3476 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3477 *start = 2;
3478 break;
bsalomonedc743a2016-06-01 09:42:31 -07003479 }
bsalomonedc743a2016-06-01 09:42:31 -07003480 return true;
3481}
bsalomon21af9ca2016-08-25 12:29:23 -07003482
3483void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3484 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3485 SkASSERT(!oval.isEmpty());
3486 SkASSERT(sweepAngle);
3487
3488 path->reset();
3489 path->setIsVolatile(true);
3490 path->setFillType(SkPath::kWinding_FillType);
3491 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3492 path->addOval(oval);
3493 return;
3494 }
3495 if (useCenter) {
3496 path->moveTo(oval.centerX(), oval.centerY());
3497 }
3498 // Arc to mods at 360 and drawArc is not supposed to.
3499 bool forceMoveTo = !useCenter;
3500 while (sweepAngle <= -360.f) {
3501 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3502 startAngle -= 180.f;
3503 path->arcTo(oval, startAngle, -180.f, false);
3504 startAngle -= 180.f;
3505 forceMoveTo = false;
3506 sweepAngle += 360.f;
3507 }
3508 while (sweepAngle >= 360.f) {
3509 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3510 startAngle += 180.f;
3511 path->arcTo(oval, startAngle, 180.f, false);
3512 startAngle += 180.f;
3513 forceMoveTo = false;
3514 sweepAngle -= 360.f;
3515 }
3516 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3517 if (useCenter) {
3518 path->close();
3519 }
3520}
Mike Reed0d7dac82017-02-02 17:45:56 -08003521
3522///////////////////////////////////////////////////////////////////////////////////////////////////
3523#include "SkNx.h"
3524
3525static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3526 SkScalar ts[2];
3527 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3528 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3529 SkASSERT(n >= 0 && n <= 2);
3530 for (int i = 0; i < n; ++i) {
3531 extremas[i] = SkEvalQuadAt(src, ts[i]);
3532 }
3533 extremas[n] = src[2];
3534 return n + 1;
3535}
3536
3537static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3538 SkConic conic(src[0], src[1], src[2], w);
3539 SkScalar ts[2];
3540 int n = conic.findXExtrema(ts);
3541 n += conic.findYExtrema(&ts[n]);
3542 SkASSERT(n >= 0 && n <= 2);
3543 for (int i = 0; i < n; ++i) {
3544 extremas[i] = conic.evalAt(ts[i]);
3545 }
3546 extremas[n] = src[2];
3547 return n + 1;
3548}
3549
3550static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3551 SkScalar ts[4];
3552 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3553 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3554 SkASSERT(n >= 0 && n <= 4);
3555 for (int i = 0; i < n; ++i) {
3556 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3557 }
3558 extremas[n] = src[3];
3559 return n + 1;
3560}
3561
Mike Reed8d3196b2017-02-03 11:34:13 -05003562SkRect SkPath::computeTightBounds() const {
3563 if (0 == this->countVerbs()) {
3564 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003565 }
3566
Mike Reed8d3196b2017-02-03 11:34:13 -05003567 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3568 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003569 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003570
Mike Reed0d7dac82017-02-02 17:45:56 -08003571 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3572 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003573 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003574
3575 // initial with the first MoveTo, so we don't have to check inside the switch
3576 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003577 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003578 for (;;) {
3579 int count = 0;
3580 switch (iter.next(pts)) {
3581 case SkPath::kMove_Verb:
3582 extremas[0] = pts[0];
3583 count = 1;
3584 break;
3585 case SkPath::kLine_Verb:
3586 extremas[0] = pts[1];
3587 count = 1;
3588 break;
3589 case SkPath::kQuad_Verb:
3590 count = compute_quad_extremas(pts, extremas);
3591 break;
3592 case SkPath::kConic_Verb:
3593 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3594 break;
3595 case SkPath::kCubic_Verb:
3596 count = compute_cubic_extremas(pts, extremas);
3597 break;
3598 case SkPath::kClose_Verb:
3599 break;
3600 case SkPath::kDone_Verb:
3601 goto DONE;
3602 }
3603 for (int i = 0; i < count; ++i) {
3604 Sk2s tmp = from_point(extremas[i]);
3605 min = Sk2s::Min(min, tmp);
3606 max = Sk2s::Max(max, tmp);
3607 }
3608 }
3609DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003610 SkRect bounds;
3611 min.store((SkPoint*)&bounds.fLeft);
3612 max.store((SkPoint*)&bounds.fRight);
3613 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003614}
Cary Clarkdf429f32017-11-08 11:44:31 -05003615
3616bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3617 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3618}
3619
3620bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3621 const SkPoint& p3, bool exact) {
3622 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3623 SkPointPriv::EqualsWithinTolerance(p2, p3);
3624}
3625
3626bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3627 const SkPoint& p3, const SkPoint& p4, bool exact) {
3628 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3629 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3630 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3631 SkPointPriv::EqualsWithinTolerance(p3, p4);
3632}