blob: a053c767dc77050fc090d247a27c40f7f1975990 [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"
reed@google.com4ed0fb72012-12-12 20:48:18 +000017#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
Mike Reeda99b6ce2017-02-04 11:04:26 -050019static float poly_eval(float A, float B, float C, float t) {
20 return (A * t + B) * t + C;
21}
22
23static float poly_eval(float A, float B, float C, float D, float t) {
24 return ((A * t + B) * t + C) * t + D;
25}
26
reed@android.com8a1c16f2008-12-17 15:59:43 +000027////////////////////////////////////////////////////////////////////////////
28
reed@google.com3563c9e2011-11-14 19:34:57 +000029/**
30 * Path.bounds is defined to be the bounds of all the control points.
31 * If we called bounds.join(r) we would skip r if r was empty, which breaks
32 * our promise. Hence we have a custom joiner that doesn't look at emptiness
33 */
34static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
35 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
36 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
37 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
38 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
39}
40
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000041static bool is_degenerate(const SkPath& path) {
42 SkPath::Iter iter(path, false);
43 SkPoint pts[4];
44 return SkPath::kDone_Verb == iter.next(pts);
45}
46
bsalomon@google.com30c174b2012-11-13 14:36:42 +000047class SkAutoDisableDirectionCheck {
48public:
49 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070050 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000051 }
52
53 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070054 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000055 }
56
57private:
reed026beb52015-06-10 14:23:15 -070058 SkPath* fPath;
59 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000061#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062
reed@android.com8a1c16f2008-12-17 15:59:43 +000063/* This guy's constructor/destructor bracket a path editing operation. It is
64 used when we know the bounds of the amount we are going to add to the path
65 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000066
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000068 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000069 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000070
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000071 It also notes if the path was originally degenerate, and if so, sets
72 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000073 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000074 */
75class SkAutoPathBoundsUpdate {
76public:
77 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
78 this->init(path);
79 }
80
81 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
82 SkScalar right, SkScalar bottom) {
83 fRect.set(left, top, right, bottom);
84 this->init(path);
85 }
reed@google.comabf15c12011-01-18 20:35:51 +000086
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000088 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
89 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000090 if (fEmpty || fHasValidBounds) {
91 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 }
93 }
reed@google.comabf15c12011-01-18 20:35:51 +000094
reed@android.com8a1c16f2008-12-17 15:59:43 +000095private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000096 SkPath* fPath;
97 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000099 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000100 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000101
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000103 // Cannot use fRect for our bounds unless we know it is sorted
104 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000106 // Mark the path's bounds as dirty if (1) they are, or (2) the path
107 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000108 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 if (fHasValidBounds && !fEmpty) {
111 joinNoEmptyChecks(&fRect, fPath->getBounds());
112 }
113 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 }
115};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000116#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118////////////////////////////////////////////////////////////////////////////
119
120/*
121 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000122 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
124
125 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000126 1. If we encounter degenerate segments, remove them
127 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
128 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
129 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130*/
131
132////////////////////////////////////////////////////////////////////////////
133
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000134// flag to require a moveTo if we begin with something else, like lineTo etc.
135#define INITIAL_LASTMOVETOINDEX_VALUE ~0
136
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000137SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800138 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000139 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700140 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141}
142
143void SkPath::resetFields() {
144 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000145 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000147 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700148 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000149
150 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700151 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000152}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000155 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 this->copyFields(that);
157 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158}
159
160SkPath::~SkPath() {
161 SkDEBUGCODE(this->validate();)
162}
163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164SkPath& SkPath::operator=(const SkPath& that) {
165 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167 if (this != &that) {
168 fPathRef.reset(SkRef(that.fPathRef.get()));
169 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700179 fIsVolatile = that.fIsVolatile;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400180
181 // Non-atomic assignment of atomic values.
182 fConvexity .store(that.fConvexity .load());
183 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184}
185
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000186bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000187 // note: don't need to look at isConvex or bounds, since just comparing the
188 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000189 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000190 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000191}
192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700195 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000196 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700198 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400199
200 // Non-atomic swaps of atomic values.
201 Convexity c = fConvexity.load();
202 fConvexity.store(that.fConvexity.load());
203 that.fConvexity.store(c);
204
205 uint8_t fd = fFirstDirection.load();
206 fFirstDirection.store(that.fFirstDirection.load());
207 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208 }
209}
210
caryclark8e7b19d2016-02-18 04:11:48 -0800211bool SkPath::isInterpolatable(const SkPath& compare) const {
212 int count = fPathRef->countVerbs();
213 if (count != compare.fPathRef->countVerbs()) {
214 return false;
215 }
216 if (!count) {
217 return true;
218 }
219 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
220 count)) {
221 return false;
222 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800223 return !fPathRef->countWeights() ||
224 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800225 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
226}
227
228bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
229 int verbCount = fPathRef->countVerbs();
230 if (verbCount != ending.fPathRef->countVerbs()) {
231 return false;
232 }
caryclarka1105382016-02-18 06:13:25 -0800233 if (!verbCount) {
234 return true;
235 }
caryclark8e7b19d2016-02-18 04:11:48 -0800236 out->reset();
237 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700238 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800239 return true;
240}
241
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000242static inline bool check_edge_against_rect(const SkPoint& p0,
243 const SkPoint& p1,
244 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700245 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246 const SkPoint* edgeBegin;
247 SkVector v;
reed026beb52015-06-10 14:23:15 -0700248 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000249 v = p1 - p0;
250 edgeBegin = &p0;
251 } else {
252 v = p0 - p1;
253 edgeBegin = &p1;
254 }
255 if (v.fX || v.fY) {
256 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500257 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
258 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
259 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
260 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000261 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
262 return false;
263 }
264 }
265 return true;
266}
267
268bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
269 // This only handles non-degenerate convex paths currently.
270 if (kConvex_Convexity != this->getConvexity()) {
271 return false;
272 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000273
reed026beb52015-06-10 14:23:15 -0700274 SkPathPriv::FirstDirection direction;
275 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000276 return false;
277 }
278
279 SkPoint firstPt;
280 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500281 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282 SkPath::Verb verb;
283 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400284 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000286 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287
Lee Salzmana19f0242017-01-12 13:06:21 -0500288 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 int nextPt = -1;
290 switch (verb) {
291 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 SkDEBUGCODE(++moveCnt);
294 firstPt = prevPt = pts[0];
295 break;
296 case kLine_Verb:
297 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400299 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 break;
301 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000302 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400304 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 nextPt = 2;
306 break;
307 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000308 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400309 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000310 nextPt = 3;
311 break;
312 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000313 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 break;
315 default:
316 SkDEBUGFAIL("unknown verb");
317 }
318 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800319 if (SkPath::kConic_Verb == verb) {
320 SkConic orig;
321 orig.set(pts, iter.conicWeight());
322 SkPoint quadPts[5];
323 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800324 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800325
326 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
327 return false;
328 }
329 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
330 return false;
331 }
332 } else {
333 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
334 return false;
335 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000336 }
337 prevPt = pts[nextPt];
338 }
339 }
340
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400341 if (segmentCount) {
342 return check_edge_against_rect(prevPt, firstPt, rect, direction);
343 }
344 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000345}
346
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000347uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000348 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800349#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400350 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
351 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352#endif
353 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000354}
djsollen@google.come63793a2012-03-21 15:39:03 +0000355
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356void SkPath::reset() {
357 SkDEBUGCODE(this->validate();)
358
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000359 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000360 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361}
362
363void SkPath::rewind() {
364 SkDEBUGCODE(this->validate();)
365
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000366 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000367 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368}
369
fsb1475b02016-01-20 09:51:07 -0800370bool SkPath::isLastContourClosed() const {
371 int verbCount = fPathRef->countVerbs();
372 if (0 == verbCount) {
373 return false;
374 }
375 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
376}
377
reed@google.com7e6c4d12012-05-10 14:05:43 +0000378bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000379 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000380
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000381 if (2 == verbCount) {
382 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
383 if (kLine_Verb == fPathRef->atVerb(1)) {
384 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000385 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000386 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000387 line[0] = pts[0];
388 line[1] = pts[1];
389 }
390 return true;
391 }
392 }
393 return false;
394}
395
caryclark@google.comf1316942011-07-26 19:54:45 +0000396/*
397 Determines if path is a rect by keeping track of changes in direction
398 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400 The direction is computed such that:
401 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000402 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000403 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000404 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000405
caryclark@google.comf1316942011-07-26 19:54:45 +0000406A rectangle cycles up/right/down/left or up/left/down/right.
407
408The test fails if:
409 The path is closed, and followed by a line.
410 A second move creates a new endpoint.
411 A diagonal line is parsed.
412 There's more than four changes of direction.
413 There's a discontinuity on the line (e.g., a move in the middle)
414 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000415 The path contains a quadratic or cubic.
416 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000417 *The rectangle doesn't complete a cycle.
418 *The final point isn't equal to the first point.
419
420 *These last two conditions we relax if we have a 3-edge path that would
421 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000422
caryclark@google.comf1316942011-07-26 19:54:45 +0000423It's OK if the path has:
424 Several colinear line segments composing a rectangle side.
425 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427The direction takes advantage of the corners found since opposite sides
428must travel in opposite directions.
429
430FIXME: Allow colinear quads and cubics to be treated like lines.
431FIXME: If the API passes fill-only, return true if the filled stroke
432 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000433
434 first,last,next direction state-machine:
435 0x1 is set if the segment is horizontal
436 0x2 is set if the segment is moving to the right or down
437 thus:
438 two directions are opposites iff (dirA ^ dirB) == 0x2
439 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000440
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000442static int rect_make_dir(SkScalar dx, SkScalar dy) {
443 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
444}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000445bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
446 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 int corners = 0;
448 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000449 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700450 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000451 first.set(0, 0);
452 last.set(0, 0);
453 int firstDirection = 0;
454 int lastDirection = 0;
455 int nextDirection = 0;
456 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700458 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000459 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000460 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700461 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
462 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000464 savePts = pts;
465 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700467 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 case kLine_Verb: {
469 SkScalar left = last.fX;
470 SkScalar top = last.fY;
471 SkScalar right = pts->fX;
472 SkScalar bottom = pts->fY;
473 ++pts;
474 if (left != right && top != bottom) {
475 return false; // diagonal
476 }
477 if (left == right && top == bottom) {
478 break; // single point on side OK
479 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000480 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 if (0 == corners) {
482 firstDirection = nextDirection;
483 first = last;
484 last = pts[-1];
485 corners = 1;
486 closedOrMoved = false;
487 break;
488 }
489 if (closedOrMoved) {
490 return false; // closed followed by a line
491 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000492 if (autoClose && nextDirection == firstDirection) {
493 break; // colinear with first
494 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 closedOrMoved = autoClose;
496 if (lastDirection != nextDirection) {
497 if (++corners > 4) {
498 return false; // too many direction changes
499 }
500 }
501 last = pts[-1];
502 if (lastDirection == nextDirection) {
503 break; // colinear segment
504 }
505 // Possible values for corners are 2, 3, and 4.
506 // When corners == 3, nextDirection opposes firstDirection.
507 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000508 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
510 if ((directionCycle ^ turn) != nextDirection) {
511 return false; // direction didn't follow cycle
512 }
513 break;
514 }
515 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000516 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000517 case kCubic_Verb:
518 return false; // quadratic, cubic not allowed
519 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700520 if (allowPartial && !autoClose && firstDirection) {
521 insertClose = true;
522 *currVerb -= 1; // try move again afterwards
523 goto addMissingClose;
524 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 last = *pts++;
526 closedOrMoved = true;
527 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000528 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000529 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000530 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000531 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700534addMissingClose:
535 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000536 }
537 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000538 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000539 if (!result) {
540 // check if we are just an incomplete rectangle, in which case we can
541 // return true, but not claim to be closed.
542 // e.g.
543 // 3 sided rectangle
544 // 4 sided but the last edge is not long enough to reach the start
545 //
546 SkScalar closeX = first.x() - last.x();
547 SkScalar closeY = first.y() - last.y();
548 if (closeX && closeY) {
549 return false; // we're diagonal, abort (can we ever reach this?)
550 }
551 int closeDirection = rect_make_dir(closeX, closeY);
552 // make sure the close-segment doesn't double-back on itself
553 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
554 result = true;
555 autoClose = false; // we are not closed
556 }
557 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000558 if (savePts) {
559 *ptsPtr = savePts;
560 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000561 if (result && isClosed) {
562 *isClosed = autoClose;
563 }
564 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000565 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return result;
568}
569
robertphillips4f662e62014-12-29 14:06:51 -0800570bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000571 SkDEBUGCODE(this->validate();)
572 int currVerb = 0;
573 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800574 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800575 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800576 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000577 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800578 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800579 int32_t num = SkToS32(pts - first);
580 if (num) {
581 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800582 } else {
583 // 'pts' isn't updated for open rects
584 *rect = this->getBounds();
585 }
586 }
587 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000588}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000589
caryclark95bc5f32015-04-08 08:34:15 -0700590bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 SkDEBUGCODE(this->validate();)
592 int currVerb = 0;
593 const SkPoint* pts = fPathRef->points();
594 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000595 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700596 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000597 return false;
598 }
599 const SkPoint* last = pts;
600 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700601 bool isClosed;
602 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000603 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700604 if (!isClosed) {
605 pts = fPathRef->points() + fPathRef->countPoints();
606 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000607 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 if (testRects[0].contains(testRects[1])) {
609 if (rects) {
610 rects[0] = testRects[0];
611 rects[1] = testRects[1];
612 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000613 if (dirs) {
614 dirs[0] = testDirs[0];
615 dirs[1] = testDirs[1];
616 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000617 return true;
618 }
619 if (testRects[1].contains(testRects[0])) {
620 if (rects) {
621 rects[0] = testRects[1];
622 rects[1] = testRects[0];
623 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000624 if (dirs) {
625 dirs[0] = testDirs[1];
626 dirs[1] = testDirs[0];
627 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000628 return true;
629 }
630 }
631 return false;
632}
633
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634int SkPath::countPoints() const {
635 return fPathRef->countPoints();
636}
637
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000638int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 SkDEBUGCODE(this->validate();)
640
641 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000642 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800644 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000645 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000648SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
650 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000651 }
652 return SkPoint::Make(0, 0);
653}
654
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655int SkPath::countVerbs() const {
656 return fPathRef->countVerbs();
657}
658
659static inline void copy_verbs_reverse(uint8_t* inorderDst,
660 const uint8_t* reversedSrc,
661 int count) {
662 for (int i = 0; i < count; ++i) {
663 inorderDst[i] = reversedSrc[~i];
664 }
665}
666
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000667int SkPath::getVerbs(uint8_t dst[], int max) const {
668 SkDEBUGCODE(this->validate();)
669
670 SkASSERT(max >= 0);
671 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 int count = SkMin32(max, fPathRef->countVerbs());
673 copy_verbs_reverse(dst, fPathRef->verbs(), count);
674 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000675}
676
reed@google.com294dd7b2011-10-11 11:58:32 +0000677bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 SkDEBUGCODE(this->validate();)
679
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000681 if (count > 0) {
682 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000685 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000687 if (lastPt) {
688 lastPt->set(0, 0);
689 }
690 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691}
692
caryclarkaec25102015-04-29 08:28:30 -0700693void SkPath::setPt(int index, SkScalar x, SkScalar y) {
694 SkDEBUGCODE(this->validate();)
695
696 int count = fPathRef->countPoints();
697 if (count <= index) {
698 return;
699 } else {
700 SkPathRef::Editor ed(&fPathRef);
701 ed.atPoint(index)->set(x, y);
702 }
703}
704
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705void SkPath::setLastPt(SkScalar x, SkScalar y) {
706 SkDEBUGCODE(this->validate();)
707
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000708 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 if (count == 0) {
710 this->moveTo(x, y);
711 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 SkPathRef::Editor ed(&fPathRef);
713 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 }
715}
716
reed@google.com04863fa2011-05-15 04:08:24 +0000717void SkPath::setConvexity(Convexity c) {
718 if (fConvexity != c) {
719 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000720 }
721}
722
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723//////////////////////////////////////////////////////////////////////////////
724// Construction methods
725
reed026beb52015-06-10 14:23:15 -0700726#define DIRTY_AFTER_EDIT \
727 do { \
728 fConvexity = kUnknown_Convexity; \
729 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000730 } while (0)
731
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732void SkPath::incReserve(U16CPU inc) {
733 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000734 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkDEBUGCODE(this->validate();)
736}
737
738void SkPath::moveTo(SkScalar x, SkScalar y) {
739 SkDEBUGCODE(this->validate();)
740
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000741 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000743 // remember our index
744 fLastMoveToIndex = fPathRef->countPoints();
745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000746 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700747
748 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749}
750
751void SkPath::rMoveTo(SkScalar x, SkScalar y) {
752 SkPoint pt;
753 this->getLastPt(&pt);
754 this->moveTo(pt.fX + x, pt.fY + y);
755}
756
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000757void SkPath::injectMoveToIfNeeded() {
758 if (fLastMoveToIndex < 0) {
759 SkScalar x, y;
760 if (fPathRef->countVerbs() == 0) {
761 x = y = 0;
762 } else {
763 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
764 x = pt.fX;
765 y = pt.fY;
766 }
767 this->moveTo(x, y);
768 }
769}
770
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771void SkPath::lineTo(SkScalar x, SkScalar y) {
772 SkDEBUGCODE(this->validate();)
773
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000774 this->injectMoveToIfNeeded();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 SkPathRef::Editor ed(&fPathRef);
777 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778
reed@google.comb54455e2011-05-16 14:16:04 +0000779 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780}
781
782void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000783 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 SkPoint pt;
785 this->getLastPt(&pt);
786 this->lineTo(pt.fX + x, pt.fY + y);
787}
788
789void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
790 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000791
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000792 this->injectMoveToIfNeeded();
793
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000794 SkPathRef::Editor ed(&fPathRef);
795 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796 pts[0].set(x1, y1);
797 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000798
reed@google.comb54455e2011-05-16 14:16:04 +0000799 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800}
801
802void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000803 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 SkPoint pt;
805 this->getLastPt(&pt);
806 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
807}
808
reed@google.com277c3f82013-05-31 15:17:50 +0000809void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
810 SkScalar w) {
811 // check for <= 0 or NaN with this test
812 if (!(w > 0)) {
813 this->lineTo(x2, y2);
814 } else if (!SkScalarIsFinite(w)) {
815 this->lineTo(x1, y1);
816 this->lineTo(x2, y2);
817 } else if (SK_Scalar1 == w) {
818 this->quadTo(x1, y1, x2, y2);
819 } else {
820 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000821
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000822 this->injectMoveToIfNeeded();
823
reed@google.com277c3f82013-05-31 15:17:50 +0000824 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000825 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000826 pts[0].set(x1, y1);
827 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000828
reed@google.com277c3f82013-05-31 15:17:50 +0000829 DIRTY_AFTER_EDIT;
830 }
831}
832
833void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
834 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000835 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000836 SkPoint pt;
837 this->getLastPt(&pt);
838 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
839}
840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
842 SkScalar x3, SkScalar y3) {
843 SkDEBUGCODE(this->validate();)
844
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000845 this->injectMoveToIfNeeded();
846
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000847 SkPathRef::Editor ed(&fPathRef);
848 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 pts[0].set(x1, y1);
850 pts[1].set(x2, y2);
851 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852
reed@google.comb54455e2011-05-16 14:16:04 +0000853 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854}
855
856void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
857 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000858 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 SkPoint pt;
860 this->getLastPt(&pt);
861 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
862 pt.fX + x3, pt.fY + y3);
863}
864
865void SkPath::close() {
866 SkDEBUGCODE(this->validate();)
867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000868 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000870 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871 case kLine_Verb:
872 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000873 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000875 case kMove_Verb: {
876 SkPathRef::Editor ed(&fPathRef);
877 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 }
reed@google.com277c3f82013-05-31 15:17:50 +0000880 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000881 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000882 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000883 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000884 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000885 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 }
887 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000888
889 // signal that we need a moveTo to follow us (unless we're done)
890#if 0
891 if (fLastMoveToIndex >= 0) {
892 fLastMoveToIndex = ~fLastMoveToIndex;
893 }
894#else
895 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
896#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897}
898
899///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000900
fmalitac08d53e2015-11-17 09:53:29 -0800901namespace {
902
903template <unsigned N>
904class PointIterator {
905public:
906 PointIterator(SkPath::Direction dir, unsigned startIndex)
907 : fCurrent(startIndex % N)
908 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
909
910 const SkPoint& current() const {
911 SkASSERT(fCurrent < N);
912 return fPts[fCurrent];
913 }
914
915 const SkPoint& next() {
916 fCurrent = (fCurrent + fAdvance) % N;
917 return this->current();
918 }
919
920protected:
921 SkPoint fPts[N];
922
923private:
924 unsigned fCurrent;
925 unsigned fAdvance;
926};
927
928class RectPointIterator : public PointIterator<4> {
929public:
930 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
931 : PointIterator(dir, startIndex) {
932
933 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
934 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
935 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
936 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
937 }
938};
939
940class OvalPointIterator : public PointIterator<4> {
941public:
942 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
943 : PointIterator(dir, startIndex) {
944
945 const SkScalar cx = oval.centerX();
946 const SkScalar cy = oval.centerY();
947
948 fPts[0] = SkPoint::Make(cx, oval.fTop);
949 fPts[1] = SkPoint::Make(oval.fRight, cy);
950 fPts[2] = SkPoint::Make(cx, oval.fBottom);
951 fPts[3] = SkPoint::Make(oval.fLeft, cy);
952 }
953};
954
955class RRectPointIterator : public PointIterator<8> {
956public:
957 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
958 : PointIterator(dir, startIndex) {
959
960 const SkRect& bounds = rrect.getBounds();
961 const SkScalar L = bounds.fLeft;
962 const SkScalar T = bounds.fTop;
963 const SkScalar R = bounds.fRight;
964 const SkScalar B = bounds.fBottom;
965
966 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
967 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
968 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
969 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
970 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
971 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
972 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
973 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
974 }
975};
976
977} // anonymous namespace
978
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000979static void assert_known_direction(int dir) {
980 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
981}
982
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800984 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985}
986
987void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
988 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800989 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
990}
991
992void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000993 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700994 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800995 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000996 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800997 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000998
fmalitac08d53e2015-11-17 09:53:29 -0800999 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000
fmalitac08d53e2015-11-17 09:53:29 -08001001 const int kVerbs = 5; // moveTo + 3x lineTo + close
1002 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003
fmalitac08d53e2015-11-17 09:53:29 -08001004 RectPointIterator iter(rect, dir, startIndex);
1005
1006 this->moveTo(iter.current());
1007 this->lineTo(iter.next());
1008 this->lineTo(iter.next());
1009 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001011
1012 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013}
1014
reed@google.com744faba2012-05-29 19:54:52 +00001015void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1016 SkDEBUGCODE(this->validate();)
1017 if (count <= 0) {
1018 return;
1019 }
1020
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001021 fLastMoveToIndex = fPathRef->countPoints();
1022
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001023 // +close makes room for the extra kClose_Verb
1024 SkPathRef::Editor ed(&fPathRef, count+close, count);
1025
1026 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001027 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001028 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1029 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001030 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001031
reed@google.com744faba2012-05-29 19:54:52 +00001032 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001033 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001034 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001035 }
1036
reed@google.com744faba2012-05-29 19:54:52 +00001037 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001038 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001039}
1040
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041#include "SkGeometry.h"
1042
reedf90ea012015-01-29 12:03:58 -08001043static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1044 SkPoint* pt) {
1045 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001046 // Chrome uses this path to move into and out of ovals. If not
1047 // treated as a special case the moves can distort the oval's
1048 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001049 pt->set(oval.fRight, oval.centerY());
1050 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001051 } else if (0 == oval.width() && 0 == oval.height()) {
1052 // Chrome will sometimes create 0 radius round rects. Having degenerate
1053 // quad segments in the path prevents the path from being recognized as
1054 // a rect.
1055 // TODO: optimizing the case where only one of width or height is zero
1056 // should also be considered. This case, however, doesn't seem to be
1057 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001058 pt->set(oval.fRight, oval.fTop);
1059 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 }
reedf90ea012015-01-29 12:03:58 -08001061 return false;
1062}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001063
reedd5d27d92015-02-09 13:54:43 -08001064// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1065//
1066static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1067 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1068 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1069 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001070
1071 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001072 loss in radians-conversion and/or sin/cos, we may end up with coincident
1073 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1074 of drawing a nearly complete circle (good).
1075 e.g. canvas.drawArc(0, 359.99, ...)
1076 -vs- canvas.drawArc(0, 359.9, ...)
1077 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001078 */
reedd5d27d92015-02-09 13:54:43 -08001079 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001080 SkScalar sw = SkScalarAbs(sweepAngle);
1081 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1082 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1083 // make a guess at a tiny angle (in radians) to tweak by
1084 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1085 // not sure how much will be enough, so we use a loop
1086 do {
1087 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001088 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1089 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001090 }
1091 }
reedd5d27d92015-02-09 13:54:43 -08001092 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1093}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001094
reed9e779d42015-02-17 11:43:14 -08001095/**
1096 * If this returns 0, then the caller should just line-to the singlePt, else it should
1097 * ignore singlePt and append the specified number of conics.
1098 */
reedd5d27d92015-02-09 13:54:43 -08001099static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001100 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1101 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001102 SkMatrix matrix;
1103
1104 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1105 matrix.postTranslate(oval.centerX(), oval.centerY());
1106
reed9e779d42015-02-17 11:43:14 -08001107 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1108 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001109 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001110 }
1111 return count;
reedd5d27d92015-02-09 13:54:43 -08001112}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001113
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001114void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001116 SkRRect rrect;
1117 rrect.setRectRadii(rect, (const SkVector*) radii);
1118 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119}
1120
reed@google.com4ed0fb72012-12-12 20:48:18 +00001121void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001122 // legacy start indices: 6 (CW) and 7(CCW)
1123 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1124}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125
fmalitac08d53e2015-11-17 09:53:29 -08001126void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1127 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001128
fmalitac08d53e2015-11-17 09:53:29 -08001129 if (rrect.isEmpty()) {
1130 return;
reed1b28a3a2014-12-17 14:42:09 -08001131 }
fmalitac08d53e2015-11-17 09:53:29 -08001132
caryclarkda707bf2015-11-19 14:47:43 -08001133 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001134 const SkRect& bounds = rrect.getBounds();
1135
1136 if (rrect.isRect()) {
1137 // degenerate(rect) => radii points are collapsing
1138 this->addRect(bounds, dir, (startIndex + 1) / 2);
1139 } else if (rrect.isOval()) {
1140 // degenerate(oval) => line points are collapsing
1141 this->addOval(bounds, dir, startIndex / 2);
1142 } else {
1143 fFirstDirection = this->hasOnlyMoveTos() ?
1144 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1145
1146 SkAutoPathBoundsUpdate apbu(this, bounds);
1147 SkAutoDisableDirectionCheck addc(this);
1148
1149 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1150 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1151 const SkScalar weight = SK_ScalarRoot2Over2;
1152
1153 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1154 const int kVerbs = startsWithConic
1155 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1156 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1157 this->incReserve(kVerbs);
1158
1159 RRectPointIterator rrectIter(rrect, dir, startIndex);
1160 // Corner iterator indices follow the collapsed radii model,
1161 // adjusted such that the start pt is "behind" the radii start pt.
1162 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1163 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1164
1165 this->moveTo(rrectIter.current());
1166 if (startsWithConic) {
1167 for (unsigned i = 0; i < 3; ++i) {
1168 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1169 this->lineTo(rrectIter.next());
1170 }
1171 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1172 // final lineTo handled by close().
1173 } else {
1174 for (unsigned i = 0; i < 4; ++i) {
1175 this->lineTo(rrectIter.next());
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 }
1178 }
1179 this->close();
1180
caryclarkda707bf2015-11-19 14:47:43 -08001181 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001182 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001183
fmalitac08d53e2015-11-17 09:53:29 -08001184 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1185 }
1186
1187 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001188}
1189
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001191 int count = fPathRef->countVerbs();
1192 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1193 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194 if (*verbs == kLine_Verb ||
1195 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001196 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197 *verbs == kCubic_Verb) {
1198 return false;
1199 }
1200 ++verbs;
1201 }
1202 return true;
1203}
1204
Brian Osmana2318572017-07-10 15:09:26 -04001205bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1206 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001207 if (count < 2) {
1208 return true;
1209 }
Brian Osmana2318572017-07-10 15:09:26 -04001210 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001211 const SkPoint& first = *pts;
1212 for (int index = 1; index < count; ++index) {
1213 if (first != pts[index]) {
1214 return false;
1215 }
1216 }
1217 return true;
1218}
1219
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001220void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1221 Direction dir) {
1222 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001223
humper@google.com75e3ca12013-04-08 21:44:11 +00001224 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001225 return;
1226 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001227
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001228 SkRRect rrect;
1229 rrect.setRectXY(rect, rx, ry);
1230 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001231}
1232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001234 // legacy start index: 1
1235 this->addOval(oval, dir, 1);
1236}
1237
1238void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001239 assert_known_direction(dir);
1240
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001241 /* If addOval() is called after previous moveTo(),
1242 this path is still marked as an oval. This is used to
1243 fit into WebKit's calling sequences.
1244 We can't simply check isEmpty() in this case, as additional
1245 moveTo() would mark the path non empty.
1246 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001247 bool isOval = hasOnlyMoveTos();
1248 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001249 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001250 } else {
reed026beb52015-06-10 14:23:15 -07001251 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001252 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 SkAutoPathBoundsUpdate apbu(this, oval);
1256
fmalitac08d53e2015-11-17 09:53:29 -08001257 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1258 const int kVerbs = 6; // moveTo + 4x conicTo + close
1259 this->incReserve(kVerbs);
1260
1261 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1262 // The corner iterator pts are tracking "behind" the oval/radii pts.
1263 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001264 const SkScalar weight = SK_ScalarRoot2Over2;
1265
fmalitac08d53e2015-11-17 09:53:29 -08001266 this->moveTo(ovalIter.current());
1267 for (unsigned i = 0; i < 4; ++i) {
1268 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001269 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271
fmalitac08d53e2015-11-17 09:53:29 -08001272 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1273
robertphillips@google.com466310d2013-12-03 16:43:54 +00001274 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001275
bsalomon78d58d12016-05-27 09:17:04 -07001276 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1280 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001281 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 }
1283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1286 bool forceMoveTo) {
1287 if (oval.width() < 0 || oval.height() < 0) {
1288 return;
1289 }
1290
reedf90ea012015-01-29 12:03:58 -08001291 if (fPathRef->countVerbs() == 0) {
1292 forceMoveTo = true;
1293 }
1294
1295 SkPoint lonePt;
1296 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1297 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1298 return;
1299 }
1300
reedd5d27d92015-02-09 13:54:43 -08001301 SkVector startV, stopV;
1302 SkRotationDirection dir;
1303 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1304
reed9e779d42015-02-17 11:43:14 -08001305 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001306
1307 // At this point, we know that the arc is not a lone point, but startV == stopV
1308 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1309 // cannot handle it.
1310 if (startV == stopV) {
1311 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1312 SkScalar radiusX = oval.width() / 2;
1313 SkScalar radiusY = oval.height() / 2;
1314 // We cannot use SkScalarSinCos function in the next line because
1315 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1316 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001317 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001318 // make sin(endAngle) to be 0 which will then draw a dot.
1319 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1320 oval.centerY() + radiusY * sk_float_sin(endAngle));
1321 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1322 return;
1323 }
1324
reedd5d27d92015-02-09 13:54:43 -08001325 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001326 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001327 if (count) {
1328 this->incReserve(count * 2 + 1);
1329 const SkPoint& pt = conics[0].fPts[0];
1330 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1331 for (int i = 0; i < count; ++i) {
1332 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1333 }
reed9e779d42015-02-17 11:43:14 -08001334 } else {
1335 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001336 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337}
1338
caryclark55d49052016-01-23 05:07:04 -08001339// This converts the SVG arc to conics.
1340// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1341// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1342// See also SVG implementation notes:
1343// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1344// Note that arcSweep bool value is flipped from the original implementation.
1345void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1346 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001347 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001348 SkPoint srcPts[2];
1349 this->getLastPt(&srcPts[0]);
1350 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1351 // joining the endpoints.
1352 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1353 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001354 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001355 return;
1356 }
1357 // If the current point and target point for the arc are identical, it should be treated as a
1358 // zero length path. This ensures continuity in animations.
1359 srcPts[1].set(x, y);
1360 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001361 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001362 return;
1363 }
1364 rx = SkScalarAbs(rx);
1365 ry = SkScalarAbs(ry);
1366 SkVector midPointDistance = srcPts[0] - srcPts[1];
1367 midPointDistance *= 0.5f;
1368
1369 SkMatrix pointTransform;
1370 pointTransform.setRotate(-angle);
1371
1372 SkPoint transformedMidPoint;
1373 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1374 SkScalar squareRx = rx * rx;
1375 SkScalar squareRy = ry * ry;
1376 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1377 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1378
1379 // Check if the radii are big enough to draw the arc, scale radii if not.
1380 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1381 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1382 if (radiiScale > 1) {
1383 radiiScale = SkScalarSqrt(radiiScale);
1384 rx *= radiiScale;
1385 ry *= radiiScale;
1386 }
1387
1388 pointTransform.setScale(1 / rx, 1 / ry);
1389 pointTransform.preRotate(-angle);
1390
1391 SkPoint unitPts[2];
1392 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1393 SkVector delta = unitPts[1] - unitPts[0];
1394
1395 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1396 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1397
1398 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1399 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1400 scaleFactor = -scaleFactor;
1401 }
1402 delta.scale(scaleFactor);
1403 SkPoint centerPoint = unitPts[0] + unitPts[1];
1404 centerPoint *= 0.5f;
1405 centerPoint.offset(-delta.fY, delta.fX);
1406 unitPts[0] -= centerPoint;
1407 unitPts[1] -= centerPoint;
1408 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1409 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1410 SkScalar thetaArc = theta2 - theta1;
1411 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1412 thetaArc += SK_ScalarPI * 2;
1413 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1414 thetaArc -= SK_ScalarPI * 2;
1415 }
1416 pointTransform.setRotate(angle);
1417 pointTransform.preScale(rx, ry);
1418
1419 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1420 SkScalar thetaWidth = thetaArc / segments;
1421 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1422 if (!SkScalarIsFinite(t)) {
1423 return;
1424 }
1425 SkScalar startTheta = theta1;
1426 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1427 for (int i = 0; i < segments; ++i) {
1428 SkScalar endTheta = startTheta + thetaWidth;
1429 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1430
1431 unitPts[1].set(cosEndTheta, sinEndTheta);
1432 unitPts[1] += centerPoint;
1433 unitPts[0] = unitPts[1];
1434 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1435 SkPoint mapped[2];
1436 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1437 this->conicTo(mapped[0], mapped[1], w);
1438 startTheta = endTheta;
1439 }
1440}
1441
1442void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1443 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1444 SkPoint currentPoint;
1445 this->getLastPt(&currentPoint);
1446 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1447}
1448
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001449void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 if (oval.isEmpty() || 0 == sweepAngle) {
1451 return;
1452 }
1453
1454 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1455
1456 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001457 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1458 // See SkPath::addOval() docs.
1459 SkScalar startOver90 = startAngle / 90.f;
1460 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1461 SkScalar error = startOver90 - startOver90I;
1462 if (SkScalarNearlyEqual(error, 0)) {
1463 // Index 1 is at startAngle == 0.
1464 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1465 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1466 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1467 (unsigned) startIndex);
1468 return;
1469 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 }
bsalomon1978ce22016-05-31 10:42:16 -07001471 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472}
1473
1474/*
1475 Need to handle the case when the angle is sharp, and our computed end-points
1476 for the arc go behind pt1 and/or p2...
1477*/
reedc7789042015-01-29 12:59:11 -08001478void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001479 if (radius == 0) {
1480 this->lineTo(x1, y1);
1481 return;
1482 }
1483
1484 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001485
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 // need to know our prev pt so we can construct tangent vectors
1487 {
1488 SkPoint start;
1489 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001490 // Handle degenerate cases by adding a line to the first point and
1491 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 before.setNormalize(x1 - start.fX, y1 - start.fY);
1493 after.setNormalize(x2 - x1, y2 - y1);
1494 }
reed@google.comabf15c12011-01-18 20:35:51 +00001495
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 SkScalar cosh = SkPoint::DotProduct(before, after);
1497 SkScalar sinh = SkPoint::CrossProduct(before, after);
1498
1499 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001500 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 return;
1502 }
reed@google.comabf15c12011-01-18 20:35:51 +00001503
Mike Reeda99b6ce2017-02-04 11:04:26 -05001504 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505
Mike Reeda99b6ce2017-02-04 11:04:26 -05001506 SkScalar xx = x1 - dist * before.fX;
1507 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001508 after.setLength(dist);
1509 this->lineTo(xx, yy);
1510 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1511 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512}
1513
1514///////////////////////////////////////////////////////////////////////////////
1515
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001516void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 SkMatrix matrix;
1518
1519 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001520 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521}
1522
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001523void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001526 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 SkPoint pts[4];
1528 Verb verb;
1529
Cary Clark9480d822017-10-19 18:01:13 -04001530 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001531 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 while ((verb = iter.next(pts)) != kDone_Verb) {
1533 switch (verb) {
1534 case kMove_Verb:
1535 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001536 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1537 injectMoveToIfNeeded(); // In case last contour is closed
1538 this->lineTo(pts[0]);
1539 } else {
1540 this->moveTo(pts[0]);
1541 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 break;
1543 case kLine_Verb:
1544 proc(matrix, &pts[1], &pts[1], 1);
1545 this->lineTo(pts[1]);
1546 break;
1547 case kQuad_Verb:
1548 proc(matrix, &pts[1], &pts[1], 2);
1549 this->quadTo(pts[1], pts[2]);
1550 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001551 case kConic_Verb:
1552 proc(matrix, &pts[1], &pts[1], 2);
1553 this->conicTo(pts[1], pts[2], iter.conicWeight());
1554 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 case kCubic_Verb:
1556 proc(matrix, &pts[1], &pts[1], 3);
1557 this->cubicTo(pts[1], pts[2], pts[3]);
1558 break;
1559 case kClose_Verb:
1560 this->close();
1561 break;
1562 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001563 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001565 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566 }
1567}
1568
1569///////////////////////////////////////////////////////////////////////////////
1570
reed@google.com277c3f82013-05-31 15:17:50 +00001571static int pts_in_verb(unsigned verb) {
1572 static const uint8_t gPtsInVerb[] = {
1573 1, // kMove
1574 1, // kLine
1575 2, // kQuad
1576 2, // kConic
1577 3, // kCubic
1578 0, // kClose
1579 0 // kDone
1580 };
1581
1582 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1583 return gPtsInVerb[verb];
1584}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586// ignore the last point of the 1st contour
1587void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001588 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1589 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 return;
1591 }
caryclark51c56782016-11-07 05:09:28 -08001592 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1593 SkASSERT(verbsEnd[0] == kMove_Verb);
1594 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1595 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596
caryclark51c56782016-11-07 05:09:28 -08001597 while (verbs < verbsEnd) {
1598 uint8_t v = *verbs++;
1599 pts -= pts_in_verb(v);
1600 switch (v) {
1601 case kMove_Verb:
1602 // if the path has multiple contours, stop after reversing the last
1603 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001605 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 break;
1607 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001608 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001610 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001611 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001612 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001614 this->cubicTo(pts[2], pts[1], pts[0]);
1615 break;
1616 case kClose_Verb:
1617 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618 break;
1619 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001620 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 break;
1622 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 }
1624}
1625
reed@google.com63d73742012-01-10 15:33:12 +00001626void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001627 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001628
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001629 const SkPoint* pts = src.fPathRef->pointsEnd();
1630 // we will iterator through src's verbs backwards
1631 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1632 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001633 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001634
1635 bool needMove = true;
1636 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001637 while (verbs < verbsEnd) {
1638 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001639 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001640
1641 if (needMove) {
1642 --pts;
1643 this->moveTo(pts->fX, pts->fY);
1644 needMove = false;
1645 }
1646 pts -= n;
1647 switch (v) {
1648 case kMove_Verb:
1649 if (needClose) {
1650 this->close();
1651 needClose = false;
1652 }
1653 needMove = true;
1654 pts += 1; // so we see the point in "if (needMove)" above
1655 break;
1656 case kLine_Verb:
1657 this->lineTo(pts[0]);
1658 break;
1659 case kQuad_Verb:
1660 this->quadTo(pts[1], pts[0]);
1661 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001662 case kConic_Verb:
1663 this->conicTo(pts[1], pts[0], *--conicWeights);
1664 break;
reed@google.com63d73742012-01-10 15:33:12 +00001665 case kCubic_Verb:
1666 this->cubicTo(pts[2], pts[1], pts[0]);
1667 break;
1668 case kClose_Verb:
1669 needClose = true;
1670 break;
1671 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001672 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001673 }
1674 }
1675}
1676
reed@android.com8a1c16f2008-12-17 15:59:43 +00001677///////////////////////////////////////////////////////////////////////////////
1678
1679void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1680 SkMatrix matrix;
1681
1682 matrix.setTranslate(dx, dy);
1683 this->transform(matrix, dst);
1684}
1685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1687 int level = 2) {
1688 if (--level >= 0) {
1689 SkPoint tmp[7];
1690
1691 SkChopCubicAtHalf(pts, tmp);
1692 subdivide_cubic_to(path, &tmp[0], level);
1693 subdivide_cubic_to(path, &tmp[3], level);
1694 } else {
1695 path->cubicTo(pts[1], pts[2], pts[3]);
1696 }
1697}
1698
1699void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1700 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001701 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 dst = (SkPath*)this;
1703 }
1704
tomhudson@google.com8d430182011-06-06 19:11:19 +00001705 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 SkPath tmp;
1707 tmp.fFillType = fFillType;
1708
1709 SkPath::Iter iter(*this, false);
1710 SkPoint pts[4];
1711 SkPath::Verb verb;
1712
reed@google.com4a3b7142012-05-16 17:16:46 +00001713 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 switch (verb) {
1715 case kMove_Verb:
1716 tmp.moveTo(pts[0]);
1717 break;
1718 case kLine_Verb:
1719 tmp.lineTo(pts[1]);
1720 break;
1721 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001722 // promote the quad to a conic
1723 tmp.conicTo(pts[1], pts[2],
1724 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001726 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001727 tmp.conicTo(pts[1], pts[2],
1728 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001729 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 case kCubic_Verb:
1731 subdivide_cubic_to(&tmp, pts);
1732 break;
1733 case kClose_Verb:
1734 tmp.close();
1735 break;
1736 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001737 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 break;
1739 }
1740 }
1741
1742 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001743 SkPathRef::Editor ed(&dst->fPathRef);
1744 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001745 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1748
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001751 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001752 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001754
reed026beb52015-06-10 14:23:15 -07001755 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1756 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001757 } else {
1758 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001759 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1760 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001761 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001762 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1763 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001764 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001765 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001766 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001767 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001768 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001769 }
1770 }
1771
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 SkDEBUGCODE(dst->validate();)
1773 }
1774}
1775
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776///////////////////////////////////////////////////////////////////////////////
1777///////////////////////////////////////////////////////////////////////////////
1778
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001780 kEmptyContour_SegmentState, // The current contour is empty. We may be
1781 // starting processing or we may have just
1782 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1784 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1785 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786};
1787
1788SkPath::Iter::Iter() {
1789#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001790 fPts = nullptr;
1791 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001794 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795#endif
1796 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001797 fVerbs = nullptr;
1798 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 fNeedClose = false;
1800}
1801
1802SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1803 this->setPath(path, forceClose);
1804}
1805
1806void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 fPts = path.fPathRef->points();
1808 fVerbs = path.fPathRef->verbs();
1809 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001810 fConicWeights = path.fPathRef->conicWeights();
1811 if (fConicWeights) {
1812 fConicWeights -= 1; // begin one behind
1813 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001815 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 fForceClose = SkToU8(forceClose);
1817 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001818 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819}
1820
1821bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001822 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 return false;
1824 }
1825 if (fForceClose) {
1826 return true;
1827 }
1828
1829 const uint8_t* verbs = fVerbs;
1830 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001831
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 if (kMove_Verb == *(verbs - 1)) {
1833 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 }
1835
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 while (verbs > stop) {
1837 // verbs points one beyond the current verb, decrement first.
1838 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 if (kMove_Verb == v) {
1840 break;
1841 }
1842 if (kClose_Verb == v) {
1843 return true;
1844 }
1845 }
1846 return false;
1847}
1848
1849SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001850 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001852 // A special case: if both points are NaN, SkPoint::operation== returns
1853 // false, but the iterator expects that they are treated as the same.
1854 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001855 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1856 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001857 return kClose_Verb;
1858 }
1859
reed@google.com9e25dbf2012-05-15 17:05:38 +00001860 pts[0] = fLastPt;
1861 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 fLastPt = fMoveTo;
1863 fCloseLine = true;
1864 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001865 } else {
1866 pts[0] = fMoveTo;
1867 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869}
1870
reed@google.com9e25dbf2012-05-15 17:05:38 +00001871const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 if (fSegmentState == kAfterMove_SegmentState) {
1873 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001875 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1878 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001879 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001881}
1882
caryclarke8c56662015-07-14 11:19:26 -07001883void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 // We need to step over anything that will not move the current draw point
1885 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001886 const uint8_t* lastMoveVerb = nullptr;
1887 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001888 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 SkPoint lastPt = fLastPt;
1890 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 switch (verb) {
1893 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 // Keep a record of this most recent move
1895 lastMoveVerb = fVerbs;
1896 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001897 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001898 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001899 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 fPts++;
1901 break;
1902
1903 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001904 // A close when we are in a segment is always valid except when it
1905 // follows a move which follows a segment.
1906 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 return;
1908 }
1909 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001910 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 break;
1912
1913 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001914 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 if (lastMoveVerb) {
1916 fVerbs = lastMoveVerb;
1917 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001918 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 return;
1920 }
1921 return;
1922 }
1923 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 fPts++;
1926 break;
1927
reed@google.com277c3f82013-05-31 15:17:50 +00001928 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001930 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 if (lastMoveVerb) {
1932 fVerbs = lastMoveVerb;
1933 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001934 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 return;
1936 }
1937 return;
1938 }
1939 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001940 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001942 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 break;
1944
1945 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001946 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 if (lastMoveVerb) {
1948 fVerbs = lastMoveVerb;
1949 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001950 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 return;
1952 }
1953 return;
1954 }
1955 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 fPts += 3;
1958 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001959
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001961 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 }
1963 }
1964}
1965
reed@google.com4a3b7142012-05-16 17:16:46 +00001966SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001967 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 // Close the curve if requested and if there is some curve to close
1971 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001972 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 return kLine_Verb;
1974 }
1975 fNeedClose = false;
1976 return kClose_Verb;
1977 }
1978 return kDone_Verb;
1979 }
1980
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001981 // fVerbs is one beyond the current verb, decrement first
1982 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001983 const SkPoint* SK_RESTRICT srcPts = fPts;
1984 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985
1986 switch (verb) {
1987 case kMove_Verb:
1988 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001989 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990 verb = this->autoClose(pts);
1991 if (verb == kClose_Verb) {
1992 fNeedClose = false;
1993 }
1994 return (Verb)verb;
1995 }
1996 if (fVerbs == fVerbStop) { // might be a trailing moveto
1997 return kDone_Verb;
1998 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001999 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002000 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002002 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 fNeedClose = fForceClose;
2005 break;
2006 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002007 pts[0] = this->cons_moveTo();
2008 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 fLastPt = srcPts[0];
2010 fCloseLine = false;
2011 srcPts += 1;
2012 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002013 case kConic_Verb:
2014 fConicWeights += 1;
2015 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 pts[0] = this->cons_moveTo();
2018 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 fLastPt = srcPts[1];
2020 srcPts += 2;
2021 break;
2022 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002023 pts[0] = this->cons_moveTo();
2024 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025 fLastPt = srcPts[2];
2026 srcPts += 3;
2027 break;
2028 case kClose_Verb:
2029 verb = this->autoClose(pts);
2030 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002031 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 } else {
2033 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002034 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002036 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 break;
2038 }
2039 fPts = srcPts;
2040 return (Verb)verb;
2041}
2042
2043///////////////////////////////////////////////////////////////////////////////
2044
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002046 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047*/
2048
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002049size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2050 SkRect oval;
2051 SkRRect rrect;
2052 bool isCCW;
2053 unsigned start;
2054 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2055 rrect.setOval(oval);
2056 // Convert to rrect start indices.
2057 start *= 2;
2058 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2059 return false;
2060 }
2061 if (!storage) {
2062 // packed header, rrect, start index.
2063 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002064 }
2065
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002066 SkWBuffer buffer(storage);
2067 // Rewrite header's first direction based on rrect direction.
2068 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2069 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2070 packedHeader |= firstDir << kDirection_SerializationShift;
2071 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2072 buffer.write32(packedHeader);
2073 rrect.writeToBuffer(&buffer);
2074 buffer.write32(SkToS32(start));
2075 buffer.padToAlign4();
2076 return buffer.pos();
2077}
2078
2079size_t SkPath::writeToMemory(void* storage) const {
2080 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
robertphillips@google.com466310d2013-12-03 16:43:54 +00002082 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002084 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002085 (fIsVolatile << kIsVolatile_SerializationShift) |
2086 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002087 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2088 return bytes;
2089 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002090
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002091 SkWBuffer buffer(storage);
2092
2093 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2094 if (nullptr == storage) {
2095 // packed header, pathref, start index
2096 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2097 return SkAlign4(byteCount);
2098 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002099 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002100 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002101
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002102 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002103
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002104 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002105 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106}
2107
Mike Reed41a930f2017-07-26 17:33:44 -04002108sk_sp<SkData> SkPath::serialize() const {
2109 size_t size = this->writeToMemory(nullptr);
2110 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2111 this->writeToMemory(data->writable_data());
2112 return data;
2113}
2114
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002115size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002116 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002117
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002118 int32_t packed;
2119 if (!buffer.readS32(&packed)) {
2120 return 0;
2121 }
2122
reed8f086022015-06-11 14:22:19 -07002123 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002124 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2125 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2126 if (version >= kPathPrivTypeEnumVersion) {
2127 SerializationType type =
2128 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2129 switch (type) {
2130 case SerializationType::kRRect: {
2131 Direction rrectDir;
2132 SkRRect rrect;
2133 int32_t start;
2134 switch (dir) {
2135 case SkPathPriv::kCW_FirstDirection:
2136 rrectDir = kCW_Direction;
2137 break;
2138 case SkPathPriv::kCCW_FirstDirection:
2139 rrectDir = kCCW_Direction;
2140 break;
2141 default:
2142 return 0;
2143 }
2144 if (!rrect.readFromBuffer(&buffer)) {
2145 return 0;
2146 }
2147 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2148 return 0;
2149 }
2150 this->reset();
2151 this->addRRect(rrect, rrectDir, SkToUInt(start));
2152 this->setFillType(fillType);
2153 buffer.skipToAlign4();
2154 return buffer.pos();
2155 }
2156 case SerializationType::kGeneral:
2157 // Fall through to general path deserialization
2158 break;
2159 default:
2160 return 0;
2161 }
2162 }
caryclark69006412016-02-17 12:16:27 -08002163 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2164 return 0;
2165 }
mtklein1b249332015-07-07 12:21:21 -07002166
Mike Kleinb9b5de52017-09-27 13:26:22 -04002167 fConvexity.store( (Convexity)((packed >> kConvexity_SerializationShift) & 0xFF) );
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002168 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002169 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002170 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002171 if (!pathRef) {
2172 return 0;
2173 }
2174
2175 fPathRef.reset(pathRef);
2176 SkDEBUGCODE(this->validate();)
2177 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002178
reed8f086022015-06-11 14:22:19 -07002179 // compatibility check
2180 if (version < kPathPrivFirstDirection_Version) {
2181 switch (dir) { // old values
2182 case 0:
2183 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2184 break;
2185 case 1:
2186 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2187 break;
2188 case 2:
2189 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2190 break;
2191 default:
Adrienne Walkerffbdd022017-09-22 10:43:26 -07002192 return 0;
reed8f086022015-06-11 14:22:19 -07002193 }
2194 } else {
2195 fFirstDirection = dir;
2196 }
2197
ajumaf8aec582016-01-13 13:46:31 -08002198 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199}
2200
2201///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002202
Ben Wagner4d1955c2017-03-10 13:08:15 -05002203#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002204#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002205#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002206
reed@google.com51bbe752013-01-17 22:07:50 +00002207static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002208 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002209 str->append(label);
2210 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002211
reed@google.com51bbe752013-01-17 22:07:50 +00002212 const SkScalar* values = &pts[0].fX;
2213 count *= 2;
2214
2215 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002216 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002217 if (i < count - 1) {
2218 str->append(", ");
2219 }
2220 }
reed@google.com277c3f82013-05-31 15:17:50 +00002221 if (conicWeight >= 0) {
2222 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002223 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002224 }
caryclark08fa28c2014-10-23 13:08:56 -07002225 str->append(");");
reede05fed02014-12-15 07:59:53 -08002226 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002227 str->append(" // ");
2228 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002229 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002230 if (i < count - 1) {
2231 str->append(", ");
2232 }
2233 }
2234 if (conicWeight >= 0) {
2235 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002236 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002237 }
2238 }
2239 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002240}
2241
caryclarke9562592014-09-15 09:26:09 -07002242void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002243 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002244 Iter iter(*this, forceClose);
2245 SkPoint pts[4];
2246 Verb verb;
2247
reed@google.com51bbe752013-01-17 22:07:50 +00002248 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002249 char const * const gFillTypeStrs[] = {
2250 "Winding",
2251 "EvenOdd",
2252 "InverseWinding",
2253 "InverseEvenOdd",
2254 };
2255 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2256 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002257 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002258 switch (verb) {
2259 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002260 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002261 break;
2262 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002263 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002264 break;
2265 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002266 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002267 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002268 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002269 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002270 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002271 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002272 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002273 break;
2274 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002275 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002276 break;
2277 default:
2278 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2279 verb = kDone_Verb; // stop the loop
2280 break;
2281 }
caryclark1049f122015-04-20 08:31:59 -07002282 if (!wStream && builder.size()) {
2283 SkDebugf("%s", builder.c_str());
2284 builder.reset();
2285 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002286 }
caryclark66a5d8b2014-06-24 08:30:15 -07002287 if (wStream) {
2288 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002289 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002290}
2291
reed@android.come522ca52009-11-23 20:10:41 +00002292void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002293 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002294}
2295
2296void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002297 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002298}
2299
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002300
Cary Clark0461e9f2017-08-25 15:13:38 -04002301bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002302 if ((fFillType & ~3) != 0) {
2303 return false;
2304 }
reed@google.comabf15c12011-01-18 20:35:51 +00002305
djsollen@google.com077348c2012-10-22 20:23:32 +00002306#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002307 if (!fBoundsIsDirty) {
2308 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002310 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002311 if (SkToBool(fIsFinite) != isFinite) {
2312 return false;
2313 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002314
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002315 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002316 // if we're empty, fBounds may be empty but translated, so we can't
2317 // necessarily compare to bounds directly
2318 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2319 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002320 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2321 return false;
2322 }
reed@android.come522ca52009-11-23 20:10:41 +00002323 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002324 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002325 if (!fBounds.isEmpty()) {
2326 return false;
2327 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002328 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002329 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002330 if (!fBounds.contains(bounds)) {
2331 return false;
2332 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002333 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002334 }
reed@android.come522ca52009-11-23 20:10:41 +00002335 }
2336 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002337#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002338 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002339}
reed@android.come522ca52009-11-23 20:10:41 +00002340
reed@google.com04863fa2011-05-15 04:08:24 +00002341///////////////////////////////////////////////////////////////////////////////
2342
reed@google.com0b7b9822011-05-16 12:29:27 +00002343static int sign(SkScalar x) { return x < 0; }
2344#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002345
robertphillipsc506e302014-09-16 09:43:31 -07002346enum DirChange {
2347 kLeft_DirChange,
2348 kRight_DirChange,
2349 kStraight_DirChange,
2350 kBackwards_DirChange,
2351
2352 kInvalid_DirChange
2353};
2354
2355
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002356static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002357 // The error epsilon was empirically derived; worse case round rects
2358 // with a mid point outset by 2x float epsilon in tests had an error
2359 // of 12.
2360 const int epsilon = 16;
2361 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2362 return false;
2363 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002364 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002365 int aBits = SkFloatAs2sCompliment(compA);
2366 int bBits = SkFloatAs2sCompliment(compB);
2367 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002368}
2369
caryclarkb4216502015-03-02 13:02:34 -08002370static bool approximately_zero_when_compared_to(double x, double y) {
2371 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002372}
2373
caryclarkb4216502015-03-02 13:02:34 -08002374
reed@google.com04863fa2011-05-15 04:08:24 +00002375// only valid for a single contour
2376struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002377 Convexicator()
2378 : fPtCount(0)
2379 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002380 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002381 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002382 , fIsCurve(false)
2383 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002384 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002385 // warnings
djsollen2f124632016-04-29 13:53:05 -07002386 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002387 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002388 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002389 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002390 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002391
2392 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002393 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002394 }
2395
2396 SkPath::Convexity getConvexity() const { return fConvexity; }
2397
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002398 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002399 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002400
reed@google.com04863fa2011-05-15 04:08:24 +00002401 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002402 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002403 return;
2404 }
2405
2406 if (0 == fPtCount) {
2407 fCurrPt = pt;
2408 ++fPtCount;
2409 } else {
2410 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002411 SkScalar lengthSqd = vec.lengthSqd();
2412 if (!SkScalarIsFinite(lengthSqd)) {
2413 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002414 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002415 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002416 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002417 fCurrPt = pt;
2418 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002419 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 } else {
2421 SkASSERT(fPtCount > 2);
2422 this->addVec(vec);
2423 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002424
reed@google.com85b6e392011-05-15 20:25:17 +00002425 int sx = sign(vec.fX);
2426 int sy = sign(vec.fY);
2427 fDx += (sx != fSx);
2428 fDy += (sy != fSy);
2429 fSx = sx;
2430 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002431
reed@google.com85b6e392011-05-15 20:25:17 +00002432 if (fDx > 3 || fDy > 3) {
2433 fConvexity = SkPath::kConcave_Convexity;
2434 }
reed@google.com04863fa2011-05-15 04:08:24 +00002435 }
2436 }
2437 }
2438
2439 void close() {
2440 if (fPtCount > 2) {
2441 this->addVec(fFirstVec);
2442 }
2443 }
2444
caryclarkb4216502015-03-02 13:02:34 -08002445 DirChange directionChange(const SkVector& curVec) {
2446 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2447
2448 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2449 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2450 largest = SkTMax(largest, -smallest);
2451
2452 if (!almost_equal(largest, largest + cross)) {
2453 int sign = SkScalarSignAsInt(cross);
2454 if (sign) {
2455 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2456 }
2457 }
2458
2459 if (cross) {
2460 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2461 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2462 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2463 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2464 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2465 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2466 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2467 if (sign) {
2468 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2469 }
2470 }
2471 }
2472
2473 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2474 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2475 fLastVec.dot(curVec) < 0.0f) {
2476 return kBackwards_DirChange;
2477 }
2478
2479 return kStraight_DirChange;
2480 }
2481
Cary Clarkc8323aa2017-08-25 08:04:43 -04002482 bool hasBackwards() const {
2483 return fBackwards;
2484 }
caryclarkb4216502015-03-02 13:02:34 -08002485
caryclarkd3d1a982014-12-08 04:57:38 -08002486 bool isFinite() const {
2487 return fIsFinite;
2488 }
2489
caryclark5ccef572015-03-02 10:07:56 -08002490 void setCurve(bool isCurve) {
2491 fIsCurve = isCurve;
2492 }
2493
reed@google.com04863fa2011-05-15 04:08:24 +00002494private:
2495 void addVec(const SkVector& vec) {
2496 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002497 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002498 switch (dir) {
2499 case kLeft_DirChange: // fall through
2500 case kRight_DirChange:
2501 if (kInvalid_DirChange == fExpectedDir) {
2502 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002503 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2504 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002505 } else if (dir != fExpectedDir) {
2506 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002507 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002508 }
2509 fLastVec = vec;
2510 break;
2511 case kStraight_DirChange:
2512 break;
2513 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002514 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002515 // If any of the subsequent dir is non-backward, it'll be concave.
2516 // Otherwise, it's still convex.
2517 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002518 }
robertphillipsc506e302014-09-16 09:43:31 -07002519 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002520 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002521 break;
2522 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002523 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002524 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002525 }
2526 }
2527
caryclarkb4216502015-03-02 13:02:34 -08002528 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002529 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002530 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002531 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2532 // value with the current vec is deemed to be of a significant value.
2533 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002534 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002535 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002536 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002537 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002538 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002539 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002540 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002541 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002542};
2543
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002544SkPath::Convexity SkPath::internalGetConvexity() const {
2545 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002546 SkPoint pts[4];
2547 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002548 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002549
2550 int contourCount = 0;
2551 int count;
2552 Convexicator state;
2553
caryclarkd3d1a982014-12-08 04:57:38 -08002554 if (!isFinite()) {
2555 return kUnknown_Convexity;
2556 }
Brian Osman205a1262017-09-18 13:13:48 +00002557 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002558 switch (verb) {
2559 case kMove_Verb:
2560 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002561 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002562 return kConcave_Convexity;
2563 }
2564 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002565 // fall through
2566 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002567 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002568 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002569 break;
caryclark5ccef572015-03-02 10:07:56 -08002570 case kQuad_Verb:
2571 // fall through
2572 case kConic_Verb:
2573 // fall through
2574 case kCubic_Verb:
2575 count = 2 + (kCubic_Verb == verb);
2576 // As an additional enhancement, this could set curve true only
2577 // if the curve is nonlinear
2578 state.setCurve(true);
2579 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002580 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002581 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002582 state.close();
2583 count = 0;
2584 break;
2585 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002586 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002587 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002588 return kConcave_Convexity;
2589 }
2590
2591 for (int i = 1; i <= count; i++) {
2592 state.addPt(pts[i]);
2593 }
2594 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002595 if (!state.isFinite()) {
2596 return kUnknown_Convexity;
2597 }
reed@google.com04863fa2011-05-15 04:08:24 +00002598 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002599 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002600 return kConcave_Convexity;
2601 }
2602 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002603 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002604 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002605 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2606 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2607 fConvexity = Convexity::kConcave_Convexity;
2608 } else {
2609 fFirstDirection = state.getFirstDirection();
2610 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002611 }
2612 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002613}
reed@google.com69a99432012-01-10 18:00:10 +00002614
2615///////////////////////////////////////////////////////////////////////////////
2616
2617class ContourIter {
2618public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002619 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002620
2621 bool done() const { return fDone; }
2622 // if !done() then these may be called
2623 int count() const { return fCurrPtCount; }
2624 const SkPoint* pts() const { return fCurrPt; }
2625 void next();
2626
2627private:
2628 int fCurrPtCount;
2629 const SkPoint* fCurrPt;
2630 const uint8_t* fCurrVerb;
2631 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002632 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002633 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002634 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002635};
2636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002637ContourIter::ContourIter(const SkPathRef& pathRef) {
2638 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002639 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002640 fCurrPt = pathRef.points();
2641 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002642 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002643 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002644 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002645 this->next();
2646}
2647
2648void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002649 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002650 fDone = true;
2651 }
2652 if (fDone) {
2653 return;
2654 }
2655
2656 // skip pts of prev contour
2657 fCurrPt += fCurrPtCount;
2658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002659 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002660 int ptCount = 1; // moveTo
2661 const uint8_t* verbs = fCurrVerb;
2662
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002663 for (--verbs; verbs > fStopVerbs; --verbs) {
2664 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002665 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002666 goto CONTOUR_END;
2667 case SkPath::kLine_Verb:
2668 ptCount += 1;
2669 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002670 case SkPath::kConic_Verb:
2671 fCurrConicWeight += 1;
2672 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002673 case SkPath::kQuad_Verb:
2674 ptCount += 2;
2675 break;
2676 case SkPath::kCubic_Verb:
2677 ptCount += 3;
2678 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002679 case SkPath::kClose_Verb:
2680 break;
2681 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002682 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002683 break;
2684 }
2685 }
2686CONTOUR_END:
2687 fCurrPtCount = ptCount;
2688 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002689 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002690}
2691
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002692// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002693static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002694 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2695 // We may get 0 when the above subtracts underflow. We expect this to be
2696 // very rare and lazily promote to double.
2697 if (0 == cross) {
2698 double p0x = SkScalarToDouble(p0.fX);
2699 double p0y = SkScalarToDouble(p0.fY);
2700
2701 double p1x = SkScalarToDouble(p1.fX);
2702 double p1y = SkScalarToDouble(p1.fY);
2703
2704 double p2x = SkScalarToDouble(p2.fX);
2705 double p2y = SkScalarToDouble(p2.fY);
2706
2707 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2708 (p1y - p0y) * (p2x - p0x));
2709
2710 }
2711 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002712}
2713
reed@google.comc1ea60a2012-01-31 15:15:36 +00002714// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002715static int find_max_y(const SkPoint pts[], int count) {
2716 SkASSERT(count > 0);
2717 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002718 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002719 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002720 SkScalar y = pts[i].fY;
2721 if (y > max) {
2722 max = y;
2723 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002724 }
2725 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002726 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002727}
2728
reed@google.comcabaf1d2012-01-11 21:03:05 +00002729static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2730 int i = index;
2731 for (;;) {
2732 i = (i + inc) % n;
2733 if (i == index) { // we wrapped around, so abort
2734 break;
2735 }
2736 if (pts[index] != pts[i]) { // found a different point, success!
2737 break;
2738 }
2739 }
2740 return i;
2741}
2742
reed@google.comc1ea60a2012-01-31 15:15:36 +00002743/**
2744 * Starting at index, and moving forward (incrementing), find the xmin and
2745 * xmax of the contiguous points that have the same Y.
2746 */
2747static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2748 int* maxIndexPtr) {
2749 const SkScalar y = pts[index].fY;
2750 SkScalar min = pts[index].fX;
2751 SkScalar max = min;
2752 int minIndex = index;
2753 int maxIndex = index;
2754 for (int i = index + 1; i < n; ++i) {
2755 if (pts[i].fY != y) {
2756 break;
2757 }
2758 SkScalar x = pts[i].fX;
2759 if (x < min) {
2760 min = x;
2761 minIndex = i;
2762 } else if (x > max) {
2763 max = x;
2764 maxIndex = i;
2765 }
2766 }
2767 *maxIndexPtr = maxIndex;
2768 return minIndex;
2769}
2770
reed026beb52015-06-10 14:23:15 -07002771static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2772 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002773}
2774
reed@google.comac8543f2012-01-30 20:51:25 +00002775/*
2776 * We loop through all contours, and keep the computed cross-product of the
2777 * contour that contained the global y-max. If we just look at the first
2778 * contour, we may find one that is wound the opposite way (correctly) since
2779 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2780 * that is outer most (or at least has the global y-max) before we can consider
2781 * its cross product.
2782 */
reed026beb52015-06-10 14:23:15 -07002783bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002784 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2785 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002786 return true;
2787 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002788
2789 // don't want to pay the cost for computing this if it
2790 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002791 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2792 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002793 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002794 return false;
2795 }
reed@google.com69a99432012-01-10 18:00:10 +00002796
reed026beb52015-06-10 14:23:15 -07002797 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002798
reed@google.comac8543f2012-01-30 20:51:25 +00002799 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002800 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002801 SkScalar ymaxCross = 0;
2802
reed@google.com69a99432012-01-10 18:00:10 +00002803 for (; !iter.done(); iter.next()) {
2804 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002805 if (n < 3) {
2806 continue;
2807 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002808
reed@google.comcabaf1d2012-01-11 21:03:05 +00002809 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002810 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002811 int index = find_max_y(pts, n);
2812 if (pts[index].fY < ymax) {
2813 continue;
2814 }
2815
2816 // If there is more than 1 distinct point at the y-max, we take the
2817 // x-min and x-max of them and just subtract to compute the dir.
2818 if (pts[(index + 1) % n].fY == pts[index].fY) {
2819 int maxIndex;
2820 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2821 if (minIndex == maxIndex) {
2822 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002823 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002824 SkASSERT(pts[minIndex].fY == pts[index].fY);
2825 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2826 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2827 // we just subtract the indices, and let that auto-convert to
2828 // SkScalar, since we just want - or + to signal the direction.
2829 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002830 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002831 TRY_CROSSPROD:
2832 // Find a next and prev index to use for the cross-product test,
2833 // but we try to find pts that form non-zero vectors from pts[index]
2834 //
2835 // Its possible that we can't find two non-degenerate vectors, so
2836 // we have to guard our search (e.g. all the pts could be in the
2837 // same place).
2838
2839 // we pass n - 1 instead of -1 so we don't foul up % operator by
2840 // passing it a negative LH argument.
2841 int prev = find_diff_pt(pts, index, n, n - 1);
2842 if (prev == index) {
2843 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002844 continue;
2845 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002846 int next = find_diff_pt(pts, index, n, 1);
2847 SkASSERT(next != index);
2848 cross = cross_prod(pts[prev], pts[index], pts[next]);
2849 // if we get a zero and the points are horizontal, then we look at the spread in
2850 // x-direction. We really should continue to walk away from the degeneracy until
2851 // there is a divergence.
2852 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2853 // construct the subtract so we get the correct Direction below
2854 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002855 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002856 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002857
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002858 if (cross) {
2859 // record our best guess so far
2860 ymax = pts[index].fY;
2861 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002862 }
2863 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002864 if (ymaxCross) {
2865 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002866 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002867 return true;
2868 } else {
2869 return false;
2870 }
reed@google.comac8543f2012-01-30 20:51:25 +00002871}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872
2873///////////////////////////////////////////////////////////////////////////////
2874
caryclark9aacd902015-12-14 08:38:09 -08002875static bool between(SkScalar a, SkScalar b, SkScalar c) {
2876 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2877 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2878 return (a - b) * (c - b) <= 0;
2879}
2880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2882 SkScalar t) {
2883 SkScalar A = c3 + 3*(c1 - c2) - c0;
2884 SkScalar B = 3*(c2 - c1 - c1 + c0);
2885 SkScalar C = 3*(c1 - c0);
2886 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002887 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888}
2889
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890template <size_t N> static void find_minmax(const SkPoint pts[],
2891 SkScalar* minPtr, SkScalar* maxPtr) {
2892 SkScalar min, max;
2893 min = max = pts[0].fX;
2894 for (size_t i = 1; i < N; ++i) {
2895 min = SkMinScalar(min, pts[i].fX);
2896 max = SkMaxScalar(max, pts[i].fX);
2897 }
2898 *minPtr = min;
2899 *maxPtr = max;
2900}
2901
caryclark9cb5d752015-12-18 04:35:24 -08002902static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2903 if (start.fY == end.fY) {
2904 return between(start.fX, x, end.fX) && x != end.fX;
2905 } else {
2906 return x == start.fX && y == start.fY;
2907 }
2908}
2909
caryclark9aacd902015-12-14 08:38:09 -08002910static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002911 SkScalar y0 = pts[0].fY;
2912 SkScalar y3 = pts[3].fY;
2913
2914 int dir = 1;
2915 if (y0 > y3) {
2916 SkTSwap(y0, y3);
2917 dir = -1;
2918 }
2919 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 return 0;
2921 }
caryclark9cb5d752015-12-18 04:35:24 -08002922 if (checkOnCurve(x, y, pts[0], pts[3])) {
2923 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002924 return 0;
2925 }
caryclark9cb5d752015-12-18 04:35:24 -08002926 if (y == y3) {
2927 return 0;
2928 }
2929
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002930 // quickreject or quickaccept
2931 SkScalar min, max;
2932 find_minmax<4>(pts, &min, &max);
2933 if (x < min) {
2934 return 0;
2935 }
2936 if (x > max) {
2937 return dir;
2938 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002939
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002940 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002941 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002942 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002943 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002944 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002945 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002946 if (SkScalarNearlyEqual(xt, x)) {
2947 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2948 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002949 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002950 }
2951 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 return xt < x ? dir : 0;
2953}
2954
caryclark9aacd902015-12-14 08:38:09 -08002955static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002956 SkPoint dst[10];
2957 int n = SkChopCubicAtYExtrema(pts, dst);
2958 int w = 0;
2959 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002960 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002961 }
2962 return w;
2963}
2964
caryclark9aacd902015-12-14 08:38:09 -08002965static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2966 SkASSERT(src);
2967 SkASSERT(t >= 0 && t <= 1);
2968 SkScalar src2w = src[2] * w;
2969 SkScalar C = src[0];
2970 SkScalar A = src[4] - 2 * src2w + C;
2971 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002972 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002973}
2974
2975
2976static double conic_eval_denominator(SkScalar w, SkScalar t) {
2977 SkScalar B = 2 * (w - 1);
2978 SkScalar C = 1;
2979 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002980 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002981}
2982
2983static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2984 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 SkScalar y0 = pts[0].fY;
2986 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002987
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 int dir = 1;
2989 if (y0 > y2) {
2990 SkTSwap(y0, y2);
2991 dir = -1;
2992 }
caryclark9aacd902015-12-14 08:38:09 -08002993 if (y < y0 || y > y2) {
2994 return 0;
2995 }
caryclark9cb5d752015-12-18 04:35:24 -08002996 if (checkOnCurve(x, y, pts[0], pts[2])) {
2997 *onCurveCount += 1;
2998 return 0;
2999 }
caryclark9aacd902015-12-14 08:38:09 -08003000 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 return 0;
3002 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003003
caryclark9aacd902015-12-14 08:38:09 -08003004 SkScalar roots[2];
3005 SkScalar A = pts[2].fY;
3006 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3007 SkScalar C = pts[0].fY;
3008 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3009 B -= C; // B = b*w - w * yCept + yCept - a
3010 C -= y;
3011 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3012 SkASSERT(n <= 1);
3013 SkScalar xt;
3014 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003015 // zero roots are returned only when y0 == y
3016 // Need [0] if dir == 1
3017 // and [2] if dir == -1
3018 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003019 } else {
3020 SkScalar t = roots[0];
3021 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3022 }
3023 if (SkScalarNearlyEqual(xt, x)) {
3024 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3025 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003026 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003027 }
3028 }
3029 return xt < x ? dir : 0;
3030}
3031
3032static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3033 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3034 if (y0 == y1) {
3035 return true;
3036 }
3037 if (y0 < y1) {
3038 return y1 <= y2;
3039 } else {
3040 return y1 >= y2;
3041 }
3042}
3043
3044static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3045 int* onCurveCount) {
3046 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003047 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003048 // If the data points are very large, the conic may not be monotonic but may also
3049 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003050 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3051 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3052 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003053 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3054 }
3055 return w;
3056}
3057
3058static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3059 SkScalar y0 = pts[0].fY;
3060 SkScalar y2 = pts[2].fY;
3061
3062 int dir = 1;
3063 if (y0 > y2) {
3064 SkTSwap(y0, y2);
3065 dir = -1;
3066 }
3067 if (y < y0 || y > y2) {
3068 return 0;
3069 }
caryclark9cb5d752015-12-18 04:35:24 -08003070 if (checkOnCurve(x, y, pts[0], pts[2])) {
3071 *onCurveCount += 1;
3072 return 0;
3073 }
caryclark9aacd902015-12-14 08:38:09 -08003074 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003075 return 0;
3076 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003077 // bounds check on X (not required. is it faster?)
3078#if 0
3079 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3080 return 0;
3081 }
3082#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003083
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003084 SkScalar roots[2];
3085 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3086 2 * (pts[1].fY - pts[0].fY),
3087 pts[0].fY - y,
3088 roots);
3089 SkASSERT(n <= 1);
3090 SkScalar xt;
3091 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003092 // zero roots are returned only when y0 == y
3093 // Need [0] if dir == 1
3094 // and [2] if dir == -1
3095 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003096 } else {
3097 SkScalar t = roots[0];
3098 SkScalar C = pts[0].fX;
3099 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3100 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003101 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003102 }
caryclark9aacd902015-12-14 08:38:09 -08003103 if (SkScalarNearlyEqual(xt, x)) {
3104 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3105 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003106 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003107 }
3108 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003109 return xt < x ? dir : 0;
3110}
3111
caryclark9aacd902015-12-14 08:38:09 -08003112static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003113 SkPoint dst[5];
3114 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003115
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003116 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3117 n = SkChopQuadAtYExtrema(pts, dst);
3118 pts = dst;
3119 }
caryclark9aacd902015-12-14 08:38:09 -08003120 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003121 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003122 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003123 }
3124 return w;
3125}
3126
caryclark9aacd902015-12-14 08:38:09 -08003127static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003128 SkScalar x0 = pts[0].fX;
3129 SkScalar y0 = pts[0].fY;
3130 SkScalar x1 = pts[1].fX;
3131 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003132
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003133 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003134
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003135 int dir = 1;
3136 if (y0 > y1) {
3137 SkTSwap(y0, y1);
3138 dir = -1;
3139 }
caryclark9aacd902015-12-14 08:38:09 -08003140 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003141 return 0;
3142 }
caryclark9cb5d752015-12-18 04:35:24 -08003143 if (checkOnCurve(x, y, pts[0], pts[1])) {
3144 *onCurveCount += 1;
3145 return 0;
3146 }
3147 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003148 return 0;
3149 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003150 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003151
caryclark9aacd902015-12-14 08:38:09 -08003152 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003153 // zero cross means the point is on the line, and since the case where
3154 // y of the query point is at the end point is handled above, we can be
3155 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003156 if (x != x1 || y != pts[1].fY) {
3157 *onCurveCount += 1;
3158 }
caryclark9aacd902015-12-14 08:38:09 -08003159 dir = 0;
3160 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003161 dir = 0;
3162 }
3163 return dir;
3164}
3165
caryclark9aacd902015-12-14 08:38:09 -08003166static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3167 SkTDArray<SkVector>* tangents) {
3168 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3169 && !between(pts[2].fY, y, pts[3].fY)) {
3170 return;
3171 }
3172 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3173 && !between(pts[2].fX, x, pts[3].fX)) {
3174 return;
3175 }
3176 SkPoint dst[10];
3177 int n = SkChopCubicAtYExtrema(pts, dst);
3178 for (int i = 0; i <= n; ++i) {
3179 SkPoint* c = &dst[i * 3];
3180 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003181 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003182 continue;
mbarbella276e6332016-05-31 14:44:01 -07003183 }
caryclark9aacd902015-12-14 08:38:09 -08003184 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3185 if (!SkScalarNearlyEqual(x, xt)) {
3186 continue;
3187 }
3188 SkVector tangent;
3189 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3190 tangents->push(tangent);
3191 }
3192}
3193
3194static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3195 SkTDArray<SkVector>* tangents) {
3196 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3197 return;
3198 }
3199 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3200 return;
3201 }
3202 SkScalar roots[2];
3203 SkScalar A = pts[2].fY;
3204 SkScalar B = pts[1].fY * w - y * w + y;
3205 SkScalar C = pts[0].fY;
3206 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3207 B -= C; // B = b*w - w * yCept + yCept - a
3208 C -= y;
3209 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3210 for (int index = 0; index < n; ++index) {
3211 SkScalar t = roots[index];
3212 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3213 if (!SkScalarNearlyEqual(x, xt)) {
3214 continue;
3215 }
3216 SkConic conic(pts, w);
3217 tangents->push(conic.evalTangentAt(t));
3218 }
3219}
3220
3221static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3222 SkTDArray<SkVector>* tangents) {
3223 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3224 return;
3225 }
3226 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3227 return;
3228 }
3229 SkScalar roots[2];
3230 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3231 2 * (pts[1].fY - pts[0].fY),
3232 pts[0].fY - y,
3233 roots);
3234 for (int index = 0; index < n; ++index) {
3235 SkScalar t = roots[index];
3236 SkScalar C = pts[0].fX;
3237 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3238 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003239 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003240 if (!SkScalarNearlyEqual(x, xt)) {
3241 continue;
3242 }
3243 tangents->push(SkEvalQuadTangentAt(pts, t));
3244 }
3245}
3246
3247static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3248 SkTDArray<SkVector>* tangents) {
3249 SkScalar y0 = pts[0].fY;
3250 SkScalar y1 = pts[1].fY;
3251 if (!between(y0, y, y1)) {
3252 return;
3253 }
3254 SkScalar x0 = pts[0].fX;
3255 SkScalar x1 = pts[1].fX;
3256 if (!between(x0, x, x1)) {
3257 return;
3258 }
3259 SkScalar dx = x1 - x0;
3260 SkScalar dy = y1 - y0;
3261 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3262 return;
3263 }
3264 SkVector v;
3265 v.set(dx, dy);
3266 tangents->push(v);
3267}
3268
reed@google.com4db592c2013-10-30 17:39:43 +00003269static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3270 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3271}
3272
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003273bool SkPath::contains(SkScalar x, SkScalar y) const {
3274 bool isInverse = this->isInverseFillType();
3275 if (this->isEmpty()) {
3276 return isInverse;
3277 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003278
reed@google.com4db592c2013-10-30 17:39:43 +00003279 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003280 return isInverse;
3281 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003282
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003283 SkPath::Iter iter(*this, true);
3284 bool done = false;
3285 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003286 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003287 do {
3288 SkPoint pts[4];
3289 switch (iter.next(pts, false)) {
3290 case SkPath::kMove_Verb:
3291 case SkPath::kClose_Verb:
3292 break;
3293 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003294 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003295 break;
3296 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003297 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003298 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003299 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003300 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003301 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003302 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003303 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003304 break;
3305 case SkPath::kDone_Verb:
3306 done = true;
3307 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003308 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003309 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003310 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3311 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3312 if (evenOddFill) {
3313 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003314 }
caryclark9aacd902015-12-14 08:38:09 -08003315 if (w) {
3316 return !isInverse;
3317 }
3318 if (onCurveCount <= 1) {
3319 return SkToBool(onCurveCount) ^ isInverse;
3320 }
3321 if ((onCurveCount & 1) || evenOddFill) {
3322 return SkToBool(onCurveCount & 1) ^ isInverse;
3323 }
halcanary9d524f22016-03-29 09:03:52 -07003324 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003325 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3326 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003327 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003328 SkTDArray<SkVector> tangents;
3329 do {
3330 SkPoint pts[4];
3331 int oldCount = tangents.count();
3332 switch (iter.next(pts, false)) {
3333 case SkPath::kMove_Verb:
3334 case SkPath::kClose_Verb:
3335 break;
3336 case SkPath::kLine_Verb:
3337 tangent_line(pts, x, y, &tangents);
3338 break;
3339 case SkPath::kQuad_Verb:
3340 tangent_quad(pts, x, y, &tangents);
3341 break;
3342 case SkPath::kConic_Verb:
3343 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3344 break;
3345 case SkPath::kCubic_Verb:
3346 tangent_cubic(pts, x, y, &tangents);
3347 break;
3348 case SkPath::kDone_Verb:
3349 done = true;
3350 break;
3351 }
3352 if (tangents.count() > oldCount) {
3353 int last = tangents.count() - 1;
3354 const SkVector& tangent = tangents[last];
3355 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3356 tangents.remove(last);
3357 } else {
3358 for (int index = 0; index < last; ++index) {
3359 const SkVector& test = tangents[index];
3360 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003361 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3362 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003363 tangents.remove(last);
3364 tangents.removeShuffle(index);
3365 break;
3366 }
3367 }
3368 }
3369 }
3370 } while (!done);
3371 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003372}
fmalitaaa0df4e2015-12-01 09:13:23 -08003373
3374int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3375 SkScalar w, SkPoint pts[], int pow2) {
3376 const SkConic conic(p0, p1, p2, w);
3377 return conic.chopIntoQuadsPOW2(pts, pow2);
3378}
bsalomonedc743a2016-06-01 09:42:31 -07003379
3380bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3381 unsigned* start) {
3382 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3383 return false;
3384 }
3385 SkPath::RawIter iter(path);
3386 SkPoint verbPts[4];
3387 SkPath::Verb v;
3388 SkPoint rectPts[5];
3389 int rectPtCnt = 0;
3390 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3391 switch (v) {
3392 case SkPath::kMove_Verb:
3393 if (0 != rectPtCnt) {
3394 return false;
3395 }
3396 rectPts[0] = verbPts[0];
3397 ++rectPtCnt;
3398 break;
3399 case SkPath::kLine_Verb:
3400 if (5 == rectPtCnt) {
3401 return false;
3402 }
3403 rectPts[rectPtCnt] = verbPts[1];
3404 ++rectPtCnt;
3405 break;
3406 case SkPath::kClose_Verb:
3407 if (4 == rectPtCnt) {
3408 rectPts[4] = rectPts[0];
3409 rectPtCnt = 5;
3410 }
3411 break;
3412 default:
3413 return false;
3414 }
3415 }
3416 if (rectPtCnt < 5) {
3417 return false;
3418 }
3419 if (rectPts[0] != rectPts[4]) {
3420 return false;
3421 }
bsalomon057ae8a2016-07-24 05:37:26 -07003422 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3423 // and pts 1 and 2 the opposite vertical or horizontal edge).
3424 bool vec03IsVertical;
3425 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3426 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3427 // Make sure it has non-zero width and height
3428 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003429 return false;
3430 }
bsalomon057ae8a2016-07-24 05:37:26 -07003431 vec03IsVertical = true;
3432 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3433 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3434 // Make sure it has non-zero width and height
3435 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3436 return false;
3437 }
3438 vec03IsVertical = false;
3439 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003440 return false;
3441 }
bsalomon057ae8a2016-07-24 05:37:26 -07003442 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3443 // set if it is on the bottom edge.
3444 unsigned sortFlags =
3445 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3446 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3447 switch (sortFlags) {
3448 case 0b00:
3449 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3450 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3451 *start = 0;
3452 break;
3453 case 0b01:
3454 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3455 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3456 *start = 1;
3457 break;
3458 case 0b10:
3459 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3460 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3461 *start = 3;
3462 break;
3463 case 0b11:
3464 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3465 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3466 *start = 2;
3467 break;
bsalomonedc743a2016-06-01 09:42:31 -07003468 }
bsalomonedc743a2016-06-01 09:42:31 -07003469 return true;
3470}
bsalomon21af9ca2016-08-25 12:29:23 -07003471
3472void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3473 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3474 SkASSERT(!oval.isEmpty());
3475 SkASSERT(sweepAngle);
3476
3477 path->reset();
3478 path->setIsVolatile(true);
3479 path->setFillType(SkPath::kWinding_FillType);
3480 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3481 path->addOval(oval);
3482 return;
3483 }
3484 if (useCenter) {
3485 path->moveTo(oval.centerX(), oval.centerY());
3486 }
3487 // Arc to mods at 360 and drawArc is not supposed to.
3488 bool forceMoveTo = !useCenter;
3489 while (sweepAngle <= -360.f) {
3490 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3491 startAngle -= 180.f;
3492 path->arcTo(oval, startAngle, -180.f, false);
3493 startAngle -= 180.f;
3494 forceMoveTo = false;
3495 sweepAngle += 360.f;
3496 }
3497 while (sweepAngle >= 360.f) {
3498 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3499 startAngle += 180.f;
3500 path->arcTo(oval, startAngle, 180.f, false);
3501 startAngle += 180.f;
3502 forceMoveTo = false;
3503 sweepAngle -= 360.f;
3504 }
3505 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3506 if (useCenter) {
3507 path->close();
3508 }
3509}
Mike Reed0d7dac82017-02-02 17:45:56 -08003510
3511///////////////////////////////////////////////////////////////////////////////////////////////////
3512#include "SkNx.h"
3513
3514static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3515 SkScalar ts[2];
3516 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3517 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3518 SkASSERT(n >= 0 && n <= 2);
3519 for (int i = 0; i < n; ++i) {
3520 extremas[i] = SkEvalQuadAt(src, ts[i]);
3521 }
3522 extremas[n] = src[2];
3523 return n + 1;
3524}
3525
3526static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3527 SkConic conic(src[0], src[1], src[2], w);
3528 SkScalar ts[2];
3529 int n = conic.findXExtrema(ts);
3530 n += conic.findYExtrema(&ts[n]);
3531 SkASSERT(n >= 0 && n <= 2);
3532 for (int i = 0; i < n; ++i) {
3533 extremas[i] = conic.evalAt(ts[i]);
3534 }
3535 extremas[n] = src[2];
3536 return n + 1;
3537}
3538
3539static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3540 SkScalar ts[4];
3541 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3542 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3543 SkASSERT(n >= 0 && n <= 4);
3544 for (int i = 0; i < n; ++i) {
3545 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3546 }
3547 extremas[n] = src[3];
3548 return n + 1;
3549}
3550
Mike Reed8d3196b2017-02-03 11:34:13 -05003551SkRect SkPath::computeTightBounds() const {
3552 if (0 == this->countVerbs()) {
3553 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003554 }
3555
Mike Reed8d3196b2017-02-03 11:34:13 -05003556 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3557 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003558 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003559
Mike Reed0d7dac82017-02-02 17:45:56 -08003560 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3561 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003562 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003563
3564 // initial with the first MoveTo, so we don't have to check inside the switch
3565 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003566 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003567 for (;;) {
3568 int count = 0;
3569 switch (iter.next(pts)) {
3570 case SkPath::kMove_Verb:
3571 extremas[0] = pts[0];
3572 count = 1;
3573 break;
3574 case SkPath::kLine_Verb:
3575 extremas[0] = pts[1];
3576 count = 1;
3577 break;
3578 case SkPath::kQuad_Verb:
3579 count = compute_quad_extremas(pts, extremas);
3580 break;
3581 case SkPath::kConic_Verb:
3582 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3583 break;
3584 case SkPath::kCubic_Verb:
3585 count = compute_cubic_extremas(pts, extremas);
3586 break;
3587 case SkPath::kClose_Verb:
3588 break;
3589 case SkPath::kDone_Verb:
3590 goto DONE;
3591 }
3592 for (int i = 0; i < count; ++i) {
3593 Sk2s tmp = from_point(extremas[i]);
3594 min = Sk2s::Min(min, tmp);
3595 max = Sk2s::Max(max, tmp);
3596 }
3597 }
3598DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003599 SkRect bounds;
3600 min.store((SkPoint*)&bounds.fLeft);
3601 max.store((SkPoint*)&bounds.fRight);
3602 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003603}