blob: 3cbc76ad8f1dac52e61410f5b4a56534d79cd4ca [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"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070014#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000016#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070041 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000042 }
43
44 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070045 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000046 }
47
48private:
reed026beb52015-06-10 14:23:15 -070049 SkPath* fPath;
50 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000051};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800129 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700131 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132}
133
134void SkPath::resetFields() {
135 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000138 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700139 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000140
141 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700142 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000143}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000146 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 this->copyFields(that);
148 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149}
150
151SkPath::~SkPath() {
152 SkDEBUGCODE(this->validate();)
153}
154
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155SkPath& SkPath::operator=(const SkPath& that) {
156 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157
bungeman@google.coma5809a32013-06-21 15:13:34 +0000158 if (this != &that) {
159 fPathRef.reset(SkRef(that.fPathRef.get()));
160 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 }
162 SkDEBUGCODE(this->validate();)
163 return *this;
164}
165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166void SkPath::copyFields(const SkPath& that) {
167 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000168 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700171 // Simulate fFirstDirection = that.fFirstDirection;
172 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700173 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174}
175
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000176bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000177 // note: don't need to look at isConvex or bounds, since just comparing the
178 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000179 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000180 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700185 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000186 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700189 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
190 uint8_t temp = fFirstDirection;
191 fFirstDirection.store(that.fFirstDirection.load());
192 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700193 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 }
195}
196
caryclark8e7b19d2016-02-18 04:11:48 -0800197bool SkPath::isInterpolatable(const SkPath& compare) const {
198 int count = fPathRef->countVerbs();
199 if (count != compare.fPathRef->countVerbs()) {
200 return false;
201 }
202 if (!count) {
203 return true;
204 }
205 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
206 count)) {
207 return false;
208 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800209 return !fPathRef->countWeights() ||
210 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800211 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
212}
213
214bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
215 int verbCount = fPathRef->countVerbs();
216 if (verbCount != ending.fPathRef->countVerbs()) {
217 return false;
218 }
caryclarka1105382016-02-18 06:13:25 -0800219 if (!verbCount) {
220 return true;
221 }
caryclark8e7b19d2016-02-18 04:11:48 -0800222 out->reset();
223 out->addPath(*this);
224 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef);
225 return true;
226}
227
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000228static inline bool check_edge_against_rect(const SkPoint& p0,
229 const SkPoint& p1,
230 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700231 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000232 const SkPoint* edgeBegin;
233 SkVector v;
reed026beb52015-06-10 14:23:15 -0700234 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000235 v = p1 - p0;
236 edgeBegin = &p0;
237 } else {
238 v = p0 - p1;
239 edgeBegin = &p1;
240 }
241 if (v.fX || v.fY) {
242 // check the cross product of v with the vec from edgeBegin to each rect corner
243 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
244 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
245 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
246 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
247 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
248 return false;
249 }
250 }
251 return true;
252}
253
254bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
255 // This only handles non-degenerate convex paths currently.
256 if (kConvex_Convexity != this->getConvexity()) {
257 return false;
258 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000259
reed026beb52015-06-10 14:23:15 -0700260 SkPathPriv::FirstDirection direction;
261 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 return false;
263 }
264
265 SkPoint firstPt;
266 SkPoint prevPt;
267 RawIter iter(*this);
268 SkPath::Verb verb;
269 SkPoint pts[4];
270 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkDEBUGCODE(int segmentCount = 0;)
272 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273
274 while ((verb = iter.next(pts)) != kDone_Verb) {
275 int nextPt = -1;
276 switch (verb) {
277 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 SkDEBUGCODE(++moveCnt);
280 firstPt = prevPt = pts[0];
281 break;
282 case kLine_Verb:
283 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000284 SkASSERT(moveCnt && !closeCount);
285 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 break;
287 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000288 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000289 SkASSERT(moveCnt && !closeCount);
290 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 nextPt = 2;
292 break;
293 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000294 SkASSERT(moveCnt && !closeCount);
295 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000296 nextPt = 3;
297 break;
298 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000299 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 break;
301 default:
302 SkDEBUGFAIL("unknown verb");
303 }
304 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800305 if (SkPath::kConic_Verb == verb) {
306 SkConic orig;
307 orig.set(pts, iter.conicWeight());
308 SkPoint quadPts[5];
309 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800310 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800311
312 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
313 return false;
314 }
315 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
316 return false;
317 }
318 } else {
319 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
320 return false;
321 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000322 }
323 prevPt = pts[nextPt];
324 }
325 }
326
327 return check_edge_against_rect(prevPt, firstPt, rect, direction);
328}
329
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000330uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000331 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800332#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000333 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
334 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
335#endif
336 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000337}
djsollen@google.come63793a2012-03-21 15:39:03 +0000338
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339void SkPath::reset() {
340 SkDEBUGCODE(this->validate();)
341
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000342 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000343 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344}
345
346void SkPath::rewind() {
347 SkDEBUGCODE(this->validate();)
348
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000349 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000350 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
fsb1475b02016-01-20 09:51:07 -0800353bool SkPath::isLastContourClosed() const {
354 int verbCount = fPathRef->countVerbs();
355 if (0 == verbCount) {
356 return false;
357 }
358 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
359}
360
reed@google.com7e6c4d12012-05-10 14:05:43 +0000361bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000362 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000364 if (2 == verbCount) {
365 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
366 if (kLine_Verb == fPathRef->atVerb(1)) {
367 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000368 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000369 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000370 line[0] = pts[0];
371 line[1] = pts[1];
372 }
373 return true;
374 }
375 }
376 return false;
377}
378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379/*
380 Determines if path is a rect by keeping track of changes in direction
381 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000382
caryclark@google.comf1316942011-07-26 19:54:45 +0000383 The direction is computed such that:
384 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000385 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000386 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000387 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000388
caryclark@google.comf1316942011-07-26 19:54:45 +0000389A rectangle cycles up/right/down/left or up/left/down/right.
390
391The test fails if:
392 The path is closed, and followed by a line.
393 A second move creates a new endpoint.
394 A diagonal line is parsed.
395 There's more than four changes of direction.
396 There's a discontinuity on the line (e.g., a move in the middle)
397 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 The path contains a quadratic or cubic.
399 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000400 *The rectangle doesn't complete a cycle.
401 *The final point isn't equal to the first point.
402
403 *These last two conditions we relax if we have a 3-edge path that would
404 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000405
caryclark@google.comf1316942011-07-26 19:54:45 +0000406It's OK if the path has:
407 Several colinear line segments composing a rectangle side.
408 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410The direction takes advantage of the corners found since opposite sides
411must travel in opposite directions.
412
413FIXME: Allow colinear quads and cubics to be treated like lines.
414FIXME: If the API passes fill-only, return true if the filled stroke
415 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000416
417 first,last,next direction state-machine:
418 0x1 is set if the segment is horizontal
419 0x2 is set if the segment is moving to the right or down
420 thus:
421 two directions are opposites iff (dirA ^ dirB) == 0x2
422 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000423
caryclark@google.comf1316942011-07-26 19:54:45 +0000424 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000425static int rect_make_dir(SkScalar dx, SkScalar dy) {
426 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
427}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000428bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
429 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000430 int corners = 0;
431 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000432 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700433 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000434 first.set(0, 0);
435 last.set(0, 0);
436 int firstDirection = 0;
437 int lastDirection = 0;
438 int nextDirection = 0;
439 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000440 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700441 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000442 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700444 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
445 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000447 savePts = pts;
448 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000449 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700450 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 case kLine_Verb: {
452 SkScalar left = last.fX;
453 SkScalar top = last.fY;
454 SkScalar right = pts->fX;
455 SkScalar bottom = pts->fY;
456 ++pts;
457 if (left != right && top != bottom) {
458 return false; // diagonal
459 }
460 if (left == right && top == bottom) {
461 break; // single point on side OK
462 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000463 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 if (0 == corners) {
465 firstDirection = nextDirection;
466 first = last;
467 last = pts[-1];
468 corners = 1;
469 closedOrMoved = false;
470 break;
471 }
472 if (closedOrMoved) {
473 return false; // closed followed by a line
474 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 if (autoClose && nextDirection == firstDirection) {
476 break; // colinear with first
477 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 closedOrMoved = autoClose;
479 if (lastDirection != nextDirection) {
480 if (++corners > 4) {
481 return false; // too many direction changes
482 }
483 }
484 last = pts[-1];
485 if (lastDirection == nextDirection) {
486 break; // colinear segment
487 }
488 // Possible values for corners are 2, 3, and 4.
489 // When corners == 3, nextDirection opposes firstDirection.
490 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000491 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
493 if ((directionCycle ^ turn) != nextDirection) {
494 return false; // direction didn't follow cycle
495 }
496 break;
497 }
498 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000499 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 case kCubic_Verb:
501 return false; // quadratic, cubic not allowed
502 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700503 if (allowPartial && !autoClose && firstDirection) {
504 insertClose = true;
505 *currVerb -= 1; // try move again afterwards
506 goto addMissingClose;
507 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 last = *pts++;
509 closedOrMoved = true;
510 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000511 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000512 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000513 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000515 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700517addMissingClose:
518 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
520 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000521 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000522 if (!result) {
523 // check if we are just an incomplete rectangle, in which case we can
524 // return true, but not claim to be closed.
525 // e.g.
526 // 3 sided rectangle
527 // 4 sided but the last edge is not long enough to reach the start
528 //
529 SkScalar closeX = first.x() - last.x();
530 SkScalar closeY = first.y() - last.y();
531 if (closeX && closeY) {
532 return false; // we're diagonal, abort (can we ever reach this?)
533 }
534 int closeDirection = rect_make_dir(closeX, closeY);
535 // make sure the close-segment doesn't double-back on itself
536 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
537 result = true;
538 autoClose = false; // we are not closed
539 }
540 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000541 if (savePts) {
542 *ptsPtr = savePts;
543 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000544 if (result && isClosed) {
545 *isClosed = autoClose;
546 }
547 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000548 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000549 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 return result;
551}
552
robertphillips4f662e62014-12-29 14:06:51 -0800553bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000554 SkDEBUGCODE(this->validate();)
555 int currVerb = 0;
556 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800557 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800558 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800559 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800561 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800562 int32_t num = SkToS32(pts - first);
563 if (num) {
564 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800565 } else {
566 // 'pts' isn't updated for open rects
567 *rect = this->getBounds();
568 }
569 }
570 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000571}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000572
caryclark95bc5f32015-04-08 08:34:15 -0700573bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000574 SkDEBUGCODE(this->validate();)
575 int currVerb = 0;
576 const SkPoint* pts = fPathRef->points();
577 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000578 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700579 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000580 return false;
581 }
582 const SkPoint* last = pts;
583 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700584 bool isClosed;
585 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000586 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700587 if (!isClosed) {
588 pts = fPathRef->points() + fPathRef->countPoints();
589 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000590 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 if (testRects[0].contains(testRects[1])) {
592 if (rects) {
593 rects[0] = testRects[0];
594 rects[1] = testRects[1];
595 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000596 if (dirs) {
597 dirs[0] = testDirs[0];
598 dirs[1] = testDirs[1];
599 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000600 return true;
601 }
602 if (testRects[1].contains(testRects[0])) {
603 if (rects) {
604 rects[0] = testRects[1];
605 rects[1] = testRects[0];
606 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 if (dirs) {
608 dirs[0] = testDirs[1];
609 dirs[1] = testDirs[0];
610 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 return true;
612 }
613 }
614 return false;
615}
616
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000617int SkPath::countPoints() const {
618 return fPathRef->countPoints();
619}
620
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000621int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622 SkDEBUGCODE(this->validate();)
623
624 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800627 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000628 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629}
630
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000631SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
633 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000634 }
635 return SkPoint::Make(0, 0);
636}
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638int SkPath::countVerbs() const {
639 return fPathRef->countVerbs();
640}
641
642static inline void copy_verbs_reverse(uint8_t* inorderDst,
643 const uint8_t* reversedSrc,
644 int count) {
645 for (int i = 0; i < count; ++i) {
646 inorderDst[i] = reversedSrc[~i];
647 }
648}
649
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000650int SkPath::getVerbs(uint8_t dst[], int max) const {
651 SkDEBUGCODE(this->validate();)
652
653 SkASSERT(max >= 0);
654 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 int count = SkMin32(max, fPathRef->countVerbs());
656 copy_verbs_reverse(dst, fPathRef->verbs(), count);
657 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000658}
659
reed@google.com294dd7b2011-10-11 11:58:32 +0000660bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661 SkDEBUGCODE(this->validate();)
662
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000664 if (count > 0) {
665 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000668 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000670 if (lastPt) {
671 lastPt->set(0, 0);
672 }
673 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
caryclarkaec25102015-04-29 08:28:30 -0700676void SkPath::setPt(int index, SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
679 int count = fPathRef->countPoints();
680 if (count <= index) {
681 return;
682 } else {
683 SkPathRef::Editor ed(&fPathRef);
684 ed.atPoint(index)->set(x, y);
685 }
686}
687
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688void SkPath::setLastPt(SkScalar x, SkScalar y) {
689 SkDEBUGCODE(this->validate();)
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 if (count == 0) {
693 this->moveTo(x, y);
694 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor ed(&fPathRef);
696 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
698}
699
reed@google.com04863fa2011-05-15 04:08:24 +0000700void SkPath::setConvexity(Convexity c) {
701 if (fConvexity != c) {
702 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000703 }
704}
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706//////////////////////////////////////////////////////////////////////////////
707// Construction methods
708
reed026beb52015-06-10 14:23:15 -0700709#define DIRTY_AFTER_EDIT \
710 do { \
711 fConvexity = kUnknown_Convexity; \
712 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000713 } while (0)
714
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715void SkPath::incReserve(U16CPU inc) {
716 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 SkDEBUGCODE(this->validate();)
719}
720
721void SkPath::moveTo(SkScalar x, SkScalar y) {
722 SkDEBUGCODE(this->validate();)
723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000724 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000726 // remember our index
727 fLastMoveToIndex = fPathRef->countPoints();
728
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000729 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700730
731 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732}
733
734void SkPath::rMoveTo(SkScalar x, SkScalar y) {
735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->moveTo(pt.fX + x, pt.fY + y);
738}
739
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000740void SkPath::injectMoveToIfNeeded() {
741 if (fLastMoveToIndex < 0) {
742 SkScalar x, y;
743 if (fPathRef->countVerbs() == 0) {
744 x = y = 0;
745 } else {
746 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
747 x = pt.fX;
748 y = pt.fY;
749 }
750 this->moveTo(x, y);
751 }
752}
753
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754void SkPath::lineTo(SkScalar x, SkScalar y) {
755 SkDEBUGCODE(this->validate();)
756
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000757 this->injectMoveToIfNeeded();
758
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000759 SkPathRef::Editor ed(&fPathRef);
760 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761
reed@google.comb54455e2011-05-16 14:16:04 +0000762 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763}
764
765void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000766 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 SkPoint pt;
768 this->getLastPt(&pt);
769 this->lineTo(pt.fX + x, pt.fY + y);
770}
771
772void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
773 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000774
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000775 this->injectMoveToIfNeeded();
776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 SkPathRef::Editor ed(&fPathRef);
778 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 pts[0].set(x1, y1);
780 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000781
reed@google.comb54455e2011-05-16 14:16:04 +0000782 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783}
784
785void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
790}
791
reed@google.com277c3f82013-05-31 15:17:50 +0000792void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar w) {
794 // check for <= 0 or NaN with this test
795 if (!(w > 0)) {
796 this->lineTo(x2, y2);
797 } else if (!SkScalarIsFinite(w)) {
798 this->lineTo(x1, y1);
799 this->lineTo(x2, y2);
800 } else if (SK_Scalar1 == w) {
801 this->quadTo(x1, y1, x2, y2);
802 } else {
803 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000804
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000805 this->injectMoveToIfNeeded();
806
reed@google.com277c3f82013-05-31 15:17:50 +0000807 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000808 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000809 pts[0].set(x1, y1);
810 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000811
reed@google.com277c3f82013-05-31 15:17:50 +0000812 DIRTY_AFTER_EDIT;
813 }
814}
815
816void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
817 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000818 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000819 SkPoint pt;
820 this->getLastPt(&pt);
821 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
822}
823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
825 SkScalar x3, SkScalar y3) {
826 SkDEBUGCODE(this->validate();)
827
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000828 this->injectMoveToIfNeeded();
829
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 SkPathRef::Editor ed(&fPathRef);
831 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 pts[0].set(x1, y1);
833 pts[1].set(x2, y2);
834 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835
reed@google.comb54455e2011-05-16 14:16:04 +0000836 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837}
838
839void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
840 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000841 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 SkPoint pt;
843 this->getLastPt(&pt);
844 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
845 pt.fX + x3, pt.fY + y3);
846}
847
848void SkPath::close() {
849 SkDEBUGCODE(this->validate();)
850
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000853 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 case kLine_Verb:
855 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000856 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000858 case kMove_Verb: {
859 SkPathRef::Editor ed(&fPathRef);
860 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000862 }
reed@google.com277c3f82013-05-31 15:17:50 +0000863 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000864 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000865 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000866 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000867 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000868 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 }
870 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000871
872 // signal that we need a moveTo to follow us (unless we're done)
873#if 0
874 if (fLastMoveToIndex >= 0) {
875 fLastMoveToIndex = ~fLastMoveToIndex;
876 }
877#else
878 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
879#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880}
881
882///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000883
fmalitac08d53e2015-11-17 09:53:29 -0800884namespace {
885
886template <unsigned N>
887class PointIterator {
888public:
889 PointIterator(SkPath::Direction dir, unsigned startIndex)
890 : fCurrent(startIndex % N)
891 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
892
893 const SkPoint& current() const {
894 SkASSERT(fCurrent < N);
895 return fPts[fCurrent];
896 }
897
898 const SkPoint& next() {
899 fCurrent = (fCurrent + fAdvance) % N;
900 return this->current();
901 }
902
903protected:
904 SkPoint fPts[N];
905
906private:
907 unsigned fCurrent;
908 unsigned fAdvance;
909};
910
911class RectPointIterator : public PointIterator<4> {
912public:
913 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
914 : PointIterator(dir, startIndex) {
915
916 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
917 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
918 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
919 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
920 }
921};
922
923class OvalPointIterator : public PointIterator<4> {
924public:
925 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
926 : PointIterator(dir, startIndex) {
927
928 const SkScalar cx = oval.centerX();
929 const SkScalar cy = oval.centerY();
930
931 fPts[0] = SkPoint::Make(cx, oval.fTop);
932 fPts[1] = SkPoint::Make(oval.fRight, cy);
933 fPts[2] = SkPoint::Make(cx, oval.fBottom);
934 fPts[3] = SkPoint::Make(oval.fLeft, cy);
935 }
936};
937
938class RRectPointIterator : public PointIterator<8> {
939public:
940 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
941 : PointIterator(dir, startIndex) {
942
943 const SkRect& bounds = rrect.getBounds();
944 const SkScalar L = bounds.fLeft;
945 const SkScalar T = bounds.fTop;
946 const SkScalar R = bounds.fRight;
947 const SkScalar B = bounds.fBottom;
948
949 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
950 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
951 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
952 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
953 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
954 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
955 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
956 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
957 }
958};
959
960} // anonymous namespace
961
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000962static void assert_known_direction(int dir) {
963 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
964}
965
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800967 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968}
969
970void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
971 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800972 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
973}
974
975void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000976 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700977 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800978 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000979 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800980 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000981
fmalitac08d53e2015-11-17 09:53:29 -0800982 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983
fmalitac08d53e2015-11-17 09:53:29 -0800984 const int kVerbs = 5; // moveTo + 3x lineTo + close
985 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986
fmalitac08d53e2015-11-17 09:53:29 -0800987 RectPointIterator iter(rect, dir, startIndex);
988
989 this->moveTo(iter.current());
990 this->lineTo(iter.next());
991 this->lineTo(iter.next());
992 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800994
995 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996}
997
reed@google.com744faba2012-05-29 19:54:52 +0000998void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
999 SkDEBUGCODE(this->validate();)
1000 if (count <= 0) {
1001 return;
1002 }
1003
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001004 fLastMoveToIndex = fPathRef->countPoints();
1005
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001006 // +close makes room for the extra kClose_Verb
1007 SkPathRef::Editor ed(&fPathRef, count+close, count);
1008
1009 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001010 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001011 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1012 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001013 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001014
reed@google.com744faba2012-05-29 19:54:52 +00001015 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001016 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001017 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001018 }
1019
reed@google.com744faba2012-05-29 19:54:52 +00001020 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001021 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001022}
1023
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001024#include "SkGeometry.h"
1025
reedf90ea012015-01-29 12:03:58 -08001026static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1027 SkPoint* pt) {
1028 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 // Chrome uses this path to move into and out of ovals. If not
1030 // treated as a special case the moves can distort the oval's
1031 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001032 pt->set(oval.fRight, oval.centerY());
1033 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001034 } else if (0 == oval.width() && 0 == oval.height()) {
1035 // Chrome will sometimes create 0 radius round rects. Having degenerate
1036 // quad segments in the path prevents the path from being recognized as
1037 // a rect.
1038 // TODO: optimizing the case where only one of width or height is zero
1039 // should also be considered. This case, however, doesn't seem to be
1040 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001041 pt->set(oval.fRight, oval.fTop);
1042 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001043 }
reedf90ea012015-01-29 12:03:58 -08001044 return false;
1045}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001046
reedd5d27d92015-02-09 13:54:43 -08001047// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1048//
1049static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1050 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1051 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1052 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001053
1054 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001055 loss in radians-conversion and/or sin/cos, we may end up with coincident
1056 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1057 of drawing a nearly complete circle (good).
1058 e.g. canvas.drawArc(0, 359.99, ...)
1059 -vs- canvas.drawArc(0, 359.9, ...)
1060 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 */
reedd5d27d92015-02-09 13:54:43 -08001062 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001063 SkScalar sw = SkScalarAbs(sweepAngle);
1064 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1065 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1066 // make a guess at a tiny angle (in radians) to tweak by
1067 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1068 // not sure how much will be enough, so we use a loop
1069 do {
1070 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001071 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1072 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073 }
1074 }
reedd5d27d92015-02-09 13:54:43 -08001075 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1076}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001077
reed9e779d42015-02-17 11:43:14 -08001078/**
1079 * If this returns 0, then the caller should just line-to the singlePt, else it should
1080 * ignore singlePt and append the specified number of conics.
1081 */
reedd5d27d92015-02-09 13:54:43 -08001082static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001083 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1084 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001085 SkMatrix matrix;
1086
1087 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1088 matrix.postTranslate(oval.centerX(), oval.centerY());
1089
reed9e779d42015-02-17 11:43:14 -08001090 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1091 if (0 == count) {
1092 matrix.mapXY(start.x(), start.y(), singlePt);
1093 }
1094 return count;
reedd5d27d92015-02-09 13:54:43 -08001095}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001096
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001097void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099 SkRRect rrect;
1100 rrect.setRectRadii(rect, (const SkVector*) radii);
1101 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102}
1103
reed@google.com4ed0fb72012-12-12 20:48:18 +00001104void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001105 // legacy start indices: 6 (CW) and 7(CCW)
1106 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1107}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108
fmalitac08d53e2015-11-17 09:53:29 -08001109void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1110 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001111
fmalitac08d53e2015-11-17 09:53:29 -08001112 if (rrect.isEmpty()) {
1113 return;
reed1b28a3a2014-12-17 14:42:09 -08001114 }
fmalitac08d53e2015-11-17 09:53:29 -08001115
caryclarkda707bf2015-11-19 14:47:43 -08001116 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001117 const SkRect& bounds = rrect.getBounds();
1118
1119 if (rrect.isRect()) {
1120 // degenerate(rect) => radii points are collapsing
1121 this->addRect(bounds, dir, (startIndex + 1) / 2);
1122 } else if (rrect.isOval()) {
1123 // degenerate(oval) => line points are collapsing
1124 this->addOval(bounds, dir, startIndex / 2);
1125 } else {
1126 fFirstDirection = this->hasOnlyMoveTos() ?
1127 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1128
1129 SkAutoPathBoundsUpdate apbu(this, bounds);
1130 SkAutoDisableDirectionCheck addc(this);
1131
1132 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1133 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1134 const SkScalar weight = SK_ScalarRoot2Over2;
1135
1136 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1137 const int kVerbs = startsWithConic
1138 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1139 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1140 this->incReserve(kVerbs);
1141
1142 RRectPointIterator rrectIter(rrect, dir, startIndex);
1143 // Corner iterator indices follow the collapsed radii model,
1144 // adjusted such that the start pt is "behind" the radii start pt.
1145 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1146 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1147
1148 this->moveTo(rrectIter.current());
1149 if (startsWithConic) {
1150 for (unsigned i = 0; i < 3; ++i) {
1151 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1152 this->lineTo(rrectIter.next());
1153 }
1154 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1155 // final lineTo handled by close().
1156 } else {
1157 for (unsigned i = 0; i < 4; ++i) {
1158 this->lineTo(rrectIter.next());
1159 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1160 }
1161 }
1162 this->close();
1163
caryclarkda707bf2015-11-19 14:47:43 -08001164 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001165 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001166
fmalitac08d53e2015-11-17 09:53:29 -08001167 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1168 }
1169
1170 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001171}
1172
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001173bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001174 int count = fPathRef->countVerbs();
1175 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1176 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001177 if (*verbs == kLine_Verb ||
1178 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001179 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001180 *verbs == kCubic_Verb) {
1181 return false;
1182 }
1183 ++verbs;
1184 }
1185 return true;
1186}
1187
caryclarkd49a86a2016-02-22 12:44:54 -08001188bool SkPath::isZeroLength() const {
1189 int count = fPathRef->countPoints();
1190 if (count < 2) {
1191 return true;
1192 }
1193 const SkPoint* pts = fPathRef.get()->points();
1194 const SkPoint& first = *pts;
1195 for (int index = 1; index < count; ++index) {
1196 if (first != pts[index]) {
1197 return false;
1198 }
1199 }
1200 return true;
1201}
1202
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001203void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1204 Direction dir) {
1205 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001206
humper@google.com75e3ca12013-04-08 21:44:11 +00001207 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001208 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001209 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001210 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001211 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1212 return;
1213 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001214
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001215 SkRRect rrect;
1216 rrect.setRectXY(rect, rx, ry);
1217 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001218}
1219
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001221 // legacy start index: 1
1222 this->addOval(oval, dir, 1);
1223}
1224
1225void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001226 assert_known_direction(dir);
1227
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001228 /* If addOval() is called after previous moveTo(),
1229 this path is still marked as an oval. This is used to
1230 fit into WebKit's calling sequences.
1231 We can't simply check isEmpty() in this case, as additional
1232 moveTo() would mark the path non empty.
1233 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001234 bool isOval = hasOnlyMoveTos();
1235 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001236 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001237 } else {
reed026beb52015-06-10 14:23:15 -07001238 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001239 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001240
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001241 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 SkAutoPathBoundsUpdate apbu(this, oval);
1243
fmalitac08d53e2015-11-17 09:53:29 -08001244 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1245 const int kVerbs = 6; // moveTo + 4x conicTo + close
1246 this->incReserve(kVerbs);
1247
1248 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1249 // The corner iterator pts are tracking "behind" the oval/radii pts.
1250 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001251 const SkScalar weight = SK_ScalarRoot2Over2;
1252
fmalitac08d53e2015-11-17 09:53:29 -08001253 this->moveTo(ovalIter.current());
1254 for (unsigned i = 0; i < 4; ++i) {
1255 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001256 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
fmalitac08d53e2015-11-17 09:53:29 -08001259 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1260
robertphillips@google.com466310d2013-12-03 16:43:54 +00001261 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001262
bsalomon78d58d12016-05-27 09:17:04 -07001263 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001264}
1265
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1267 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001268 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 }
1270}
1271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1273 bool forceMoveTo) {
1274 if (oval.width() < 0 || oval.height() < 0) {
1275 return;
1276 }
1277
reedf90ea012015-01-29 12:03:58 -08001278 if (fPathRef->countVerbs() == 0) {
1279 forceMoveTo = true;
1280 }
1281
1282 SkPoint lonePt;
1283 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1284 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1285 return;
1286 }
1287
reedd5d27d92015-02-09 13:54:43 -08001288 SkVector startV, stopV;
1289 SkRotationDirection dir;
1290 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1291
reed9e779d42015-02-17 11:43:14 -08001292 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001293 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001294 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001295 if (count) {
1296 this->incReserve(count * 2 + 1);
1297 const SkPoint& pt = conics[0].fPts[0];
1298 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1299 for (int i = 0; i < count; ++i) {
1300 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1301 }
reed9e779d42015-02-17 11:43:14 -08001302 } else {
1303 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001304 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305}
1306
caryclark55d49052016-01-23 05:07:04 -08001307// This converts the SVG arc to conics.
1308// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1309// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1310// See also SVG implementation notes:
1311// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1312// Note that arcSweep bool value is flipped from the original implementation.
1313void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1314 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001315 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001316 SkPoint srcPts[2];
1317 this->getLastPt(&srcPts[0]);
1318 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1319 // joining the endpoints.
1320 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1321 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001322 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001323 return;
1324 }
1325 // If the current point and target point for the arc are identical, it should be treated as a
1326 // zero length path. This ensures continuity in animations.
1327 srcPts[1].set(x, y);
1328 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001329 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001330 return;
1331 }
1332 rx = SkScalarAbs(rx);
1333 ry = SkScalarAbs(ry);
1334 SkVector midPointDistance = srcPts[0] - srcPts[1];
1335 midPointDistance *= 0.5f;
1336
1337 SkMatrix pointTransform;
1338 pointTransform.setRotate(-angle);
1339
1340 SkPoint transformedMidPoint;
1341 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1342 SkScalar squareRx = rx * rx;
1343 SkScalar squareRy = ry * ry;
1344 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1345 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1346
1347 // Check if the radii are big enough to draw the arc, scale radii if not.
1348 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1349 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1350 if (radiiScale > 1) {
1351 radiiScale = SkScalarSqrt(radiiScale);
1352 rx *= radiiScale;
1353 ry *= radiiScale;
1354 }
1355
1356 pointTransform.setScale(1 / rx, 1 / ry);
1357 pointTransform.preRotate(-angle);
1358
1359 SkPoint unitPts[2];
1360 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1361 SkVector delta = unitPts[1] - unitPts[0];
1362
1363 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1364 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1365
1366 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1367 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1368 scaleFactor = -scaleFactor;
1369 }
1370 delta.scale(scaleFactor);
1371 SkPoint centerPoint = unitPts[0] + unitPts[1];
1372 centerPoint *= 0.5f;
1373 centerPoint.offset(-delta.fY, delta.fX);
1374 unitPts[0] -= centerPoint;
1375 unitPts[1] -= centerPoint;
1376 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1377 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1378 SkScalar thetaArc = theta2 - theta1;
1379 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1380 thetaArc += SK_ScalarPI * 2;
1381 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1382 thetaArc -= SK_ScalarPI * 2;
1383 }
1384 pointTransform.setRotate(angle);
1385 pointTransform.preScale(rx, ry);
1386
1387 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1388 SkScalar thetaWidth = thetaArc / segments;
1389 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1390 if (!SkScalarIsFinite(t)) {
1391 return;
1392 }
1393 SkScalar startTheta = theta1;
1394 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1395 for (int i = 0; i < segments; ++i) {
1396 SkScalar endTheta = startTheta + thetaWidth;
1397 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1398
1399 unitPts[1].set(cosEndTheta, sinEndTheta);
1400 unitPts[1] += centerPoint;
1401 unitPts[0] = unitPts[1];
1402 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1403 SkPoint mapped[2];
1404 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1405 this->conicTo(mapped[0], mapped[1], w);
1406 startTheta = endTheta;
1407 }
1408}
1409
1410void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1411 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1412 SkPoint currentPoint;
1413 this->getLastPt(&currentPoint);
1414 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1415}
1416
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001417void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 if (oval.isEmpty() || 0 == sweepAngle) {
1419 return;
1420 }
1421
1422 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1423
1424 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001425 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1426 // See SkPath::addOval() docs.
1427 SkScalar startOver90 = startAngle / 90.f;
1428 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1429 SkScalar error = startOver90 - startOver90I;
1430 if (SkScalarNearlyEqual(error, 0)) {
1431 // Index 1 is at startAngle == 0.
1432 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1433 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1434 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1435 (unsigned) startIndex);
1436 return;
1437 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 }
bsalomon1978ce22016-05-31 10:42:16 -07001439 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440}
1441
1442/*
1443 Need to handle the case when the angle is sharp, and our computed end-points
1444 for the arc go behind pt1 and/or p2...
1445*/
reedc7789042015-01-29 12:59:11 -08001446void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001447 if (radius == 0) {
1448 this->lineTo(x1, y1);
1449 return;
1450 }
1451
1452 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001453
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 // need to know our prev pt so we can construct tangent vectors
1455 {
1456 SkPoint start;
1457 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001458 // Handle degenerate cases by adding a line to the first point and
1459 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 before.setNormalize(x1 - start.fX, y1 - start.fY);
1461 after.setNormalize(x2 - x1, y2 - y1);
1462 }
reed@google.comabf15c12011-01-18 20:35:51 +00001463
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 SkScalar cosh = SkPoint::DotProduct(before, after);
1465 SkScalar sinh = SkPoint::CrossProduct(before, after);
1466
1467 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001468 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 return;
1470 }
reed@google.comabf15c12011-01-18 20:35:51 +00001471
caryclark88651ae2016-01-20 11:55:11 -08001472 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473
1474 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1475 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001476 after.setLength(dist);
1477 this->lineTo(xx, yy);
1478 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1479 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480}
1481
1482///////////////////////////////////////////////////////////////////////////////
1483
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001484void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 SkMatrix matrix;
1486
1487 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001488 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489}
1490
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001491void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001492 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001494 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 SkPoint pts[4];
1496 Verb verb;
1497
1498 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001499 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 while ((verb = iter.next(pts)) != kDone_Verb) {
1501 switch (verb) {
1502 case kMove_Verb:
1503 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001504 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1505 injectMoveToIfNeeded(); // In case last contour is closed
1506 this->lineTo(pts[0]);
1507 } else {
1508 this->moveTo(pts[0]);
1509 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 break;
1511 case kLine_Verb:
1512 proc(matrix, &pts[1], &pts[1], 1);
1513 this->lineTo(pts[1]);
1514 break;
1515 case kQuad_Verb:
1516 proc(matrix, &pts[1], &pts[1], 2);
1517 this->quadTo(pts[1], pts[2]);
1518 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001519 case kConic_Verb:
1520 proc(matrix, &pts[1], &pts[1], 2);
1521 this->conicTo(pts[1], pts[2], iter.conicWeight());
1522 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kCubic_Verb:
1524 proc(matrix, &pts[1], &pts[1], 3);
1525 this->cubicTo(pts[1], pts[2], pts[3]);
1526 break;
1527 case kClose_Verb:
1528 this->close();
1529 break;
1530 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001531 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001533 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 }
1535}
1536
1537///////////////////////////////////////////////////////////////////////////////
1538
reed@google.com277c3f82013-05-31 15:17:50 +00001539static int pts_in_verb(unsigned verb) {
1540 static const uint8_t gPtsInVerb[] = {
1541 1, // kMove
1542 1, // kLine
1543 2, // kQuad
1544 2, // kConic
1545 3, // kCubic
1546 0, // kClose
1547 0 // kDone
1548 };
1549
1550 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1551 return gPtsInVerb[verb];
1552}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554// ignore the last point of the 1st contour
1555void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 int i, vcount = path.fPathRef->countVerbs();
1557 // exit early if the path is empty, or just has a moveTo.
1558 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 return;
1560 }
1561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001562 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001564 const uint8_t* verbs = path.fPathRef->verbs();
1565 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001566 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 SkASSERT(verbs[~0] == kMove_Verb);
1569 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001570 unsigned v = verbs[~i];
1571 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (n == 0) {
1573 break;
1574 }
1575 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001576 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 }
1578
1579 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001580 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 case kLine_Verb:
1582 this->lineTo(pts[-1].fX, pts[-1].fY);
1583 break;
1584 case kQuad_Verb:
1585 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1586 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001587 case kConic_Verb:
1588 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1589 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 case kCubic_Verb:
1591 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1592 pts[-3].fX, pts[-3].fY);
1593 break;
1594 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001595 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 break;
1597 }
reed@google.com277c3f82013-05-31 15:17:50 +00001598 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 }
1600}
1601
reed@google.com63d73742012-01-10 15:33:12 +00001602void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001603 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001605 const SkPoint* pts = src.fPathRef->pointsEnd();
1606 // we will iterator through src's verbs backwards
1607 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1608 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001609 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001610
1611 bool needMove = true;
1612 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001613 while (verbs < verbsEnd) {
1614 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001615 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001616
1617 if (needMove) {
1618 --pts;
1619 this->moveTo(pts->fX, pts->fY);
1620 needMove = false;
1621 }
1622 pts -= n;
1623 switch (v) {
1624 case kMove_Verb:
1625 if (needClose) {
1626 this->close();
1627 needClose = false;
1628 }
1629 needMove = true;
1630 pts += 1; // so we see the point in "if (needMove)" above
1631 break;
1632 case kLine_Verb:
1633 this->lineTo(pts[0]);
1634 break;
1635 case kQuad_Verb:
1636 this->quadTo(pts[1], pts[0]);
1637 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001638 case kConic_Verb:
1639 this->conicTo(pts[1], pts[0], *--conicWeights);
1640 break;
reed@google.com63d73742012-01-10 15:33:12 +00001641 case kCubic_Verb:
1642 this->cubicTo(pts[2], pts[1], pts[0]);
1643 break;
1644 case kClose_Verb:
1645 needClose = true;
1646 break;
1647 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001648 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001649 }
1650 }
1651}
1652
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653///////////////////////////////////////////////////////////////////////////////
1654
1655void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1656 SkMatrix matrix;
1657
1658 matrix.setTranslate(dx, dy);
1659 this->transform(matrix, dst);
1660}
1661
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1663 int level = 2) {
1664 if (--level >= 0) {
1665 SkPoint tmp[7];
1666
1667 SkChopCubicAtHalf(pts, tmp);
1668 subdivide_cubic_to(path, &tmp[0], level);
1669 subdivide_cubic_to(path, &tmp[3], level);
1670 } else {
1671 path->cubicTo(pts[1], pts[2], pts[3]);
1672 }
1673}
1674
1675void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1676 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001677 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 dst = (SkPath*)this;
1679 }
1680
tomhudson@google.com8d430182011-06-06 19:11:19 +00001681 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001682 SkPath tmp;
1683 tmp.fFillType = fFillType;
1684
1685 SkPath::Iter iter(*this, false);
1686 SkPoint pts[4];
1687 SkPath::Verb verb;
1688
reed@google.com4a3b7142012-05-16 17:16:46 +00001689 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 switch (verb) {
1691 case kMove_Verb:
1692 tmp.moveTo(pts[0]);
1693 break;
1694 case kLine_Verb:
1695 tmp.lineTo(pts[1]);
1696 break;
1697 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001698 // promote the quad to a conic
1699 tmp.conicTo(pts[1], pts[2],
1700 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001702 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001703 tmp.conicTo(pts[1], pts[2],
1704 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001705 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 case kCubic_Verb:
1707 subdivide_cubic_to(&tmp, pts);
1708 break;
1709 case kClose_Verb:
1710 tmp.close();
1711 break;
1712 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001713 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 break;
1715 }
1716 }
1717
1718 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001719 SkPathRef::Editor ed(&dst->fPathRef);
1720 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001721 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001723 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001727 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001728 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001730
reed026beb52015-06-10 14:23:15 -07001731 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1732 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001733 } else {
1734 SkScalar det2x2 =
1735 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1736 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1737 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001738 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1739 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001740 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001741 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001742 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001743 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001744 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001745 }
1746 }
1747
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 SkDEBUGCODE(dst->validate();)
1749 }
1750}
1751
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752///////////////////////////////////////////////////////////////////////////////
1753///////////////////////////////////////////////////////////////////////////////
1754
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001755enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001756 kEmptyContour_SegmentState, // The current contour is empty. We may be
1757 // starting processing or we may have just
1758 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001759 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1760 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1761 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762};
1763
1764SkPath::Iter::Iter() {
1765#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001766 fPts = nullptr;
1767 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001770 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771#endif
1772 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001773 fVerbs = nullptr;
1774 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 fNeedClose = false;
1776}
1777
1778SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1779 this->setPath(path, forceClose);
1780}
1781
1782void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001783 fPts = path.fPathRef->points();
1784 fVerbs = path.fPathRef->verbs();
1785 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001786 fConicWeights = path.fPathRef->conicWeights();
1787 if (fConicWeights) {
1788 fConicWeights -= 1; // begin one behind
1789 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001790 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001791 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 fForceClose = SkToU8(forceClose);
1793 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001794 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795}
1796
1797bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001798 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 return false;
1800 }
1801 if (fForceClose) {
1802 return true;
1803 }
1804
1805 const uint8_t* verbs = fVerbs;
1806 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001807
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 if (kMove_Verb == *(verbs - 1)) {
1809 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 }
1811
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001812 while (verbs > stop) {
1813 // verbs points one beyond the current verb, decrement first.
1814 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 if (kMove_Verb == v) {
1816 break;
1817 }
1818 if (kClose_Verb == v) {
1819 return true;
1820 }
1821 }
1822 return false;
1823}
1824
1825SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001826 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001828 // A special case: if both points are NaN, SkPoint::operation== returns
1829 // false, but the iterator expects that they are treated as the same.
1830 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001831 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1832 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001833 return kClose_Verb;
1834 }
1835
reed@google.com9e25dbf2012-05-15 17:05:38 +00001836 pts[0] = fLastPt;
1837 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838 fLastPt = fMoveTo;
1839 fCloseLine = true;
1840 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001841 } else {
1842 pts[0] = fMoveTo;
1843 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845}
1846
reed@google.com9e25dbf2012-05-15 17:05:38 +00001847const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 if (fSegmentState == kAfterMove_SegmentState) {
1849 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001851 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1854 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001855 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857}
1858
caryclarke8c56662015-07-14 11:19:26 -07001859void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 // We need to step over anything that will not move the current draw point
1861 // forward before the next move is seen
1862 const uint8_t* lastMoveVerb = 0;
1863 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001864 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 SkPoint lastPt = fLastPt;
1866 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 switch (verb) {
1869 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 // Keep a record of this most recent move
1871 lastMoveVerb = fVerbs;
1872 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001873 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001874 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001875 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 fPts++;
1877 break;
1878
1879 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001880 // A close when we are in a segment is always valid except when it
1881 // follows a move which follows a segment.
1882 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 return;
1884 }
1885 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001886 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 break;
1888
1889 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001890 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 if (lastMoveVerb) {
1892 fVerbs = lastMoveVerb;
1893 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001894 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 return;
1896 }
1897 return;
1898 }
1899 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001900 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 fPts++;
1902 break;
1903
reed@google.com277c3f82013-05-31 15:17:50 +00001904 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001906 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 if (lastMoveVerb) {
1908 fVerbs = lastMoveVerb;
1909 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001910 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 return;
1912 }
1913 return;
1914 }
1915 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001918 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 break;
1920
1921 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001922 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 if (lastMoveVerb) {
1924 fVerbs = lastMoveVerb;
1925 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001926 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 return;
1928 }
1929 return;
1930 }
1931 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 fPts += 3;
1934 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001935
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001937 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 }
1939 }
1940}
1941
reed@google.com4a3b7142012-05-16 17:16:46 +00001942SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001943 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 // Close the curve if requested and if there is some curve to close
1947 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 return kLine_Verb;
1950 }
1951 fNeedClose = false;
1952 return kClose_Verb;
1953 }
1954 return kDone_Verb;
1955 }
1956
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001957 // fVerbs is one beyond the current verb, decrement first
1958 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001959 const SkPoint* SK_RESTRICT srcPts = fPts;
1960 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961
1962 switch (verb) {
1963 case kMove_Verb:
1964 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001965 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 verb = this->autoClose(pts);
1967 if (verb == kClose_Verb) {
1968 fNeedClose = false;
1969 }
1970 return (Verb)verb;
1971 }
1972 if (fVerbs == fVerbStop) { // might be a trailing moveto
1973 return kDone_Verb;
1974 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001975 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001976 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001978 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 fNeedClose = fForceClose;
1981 break;
1982 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001983 pts[0] = this->cons_moveTo();
1984 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 fLastPt = srcPts[0];
1986 fCloseLine = false;
1987 srcPts += 1;
1988 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001989 case kConic_Verb:
1990 fConicWeights += 1;
1991 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001993 pts[0] = this->cons_moveTo();
1994 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 fLastPt = srcPts[1];
1996 srcPts += 2;
1997 break;
1998 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 pts[0] = this->cons_moveTo();
2000 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 fLastPt = srcPts[2];
2002 srcPts += 3;
2003 break;
2004 case kClose_Verb:
2005 verb = this->autoClose(pts);
2006 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002007 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 } else {
2009 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002010 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002012 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 break;
2014 }
2015 fPts = srcPts;
2016 return (Verb)verb;
2017}
2018
2019///////////////////////////////////////////////////////////////////////////////
2020
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002022 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023*/
2024
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002025size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 SkDEBUGCODE(this->validate();)
2027
halcanary96fcdcc2015-08-27 07:41:13 -07002028 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002029 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002030 return SkAlign4(byteCount);
2031 }
2032
2033 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002034
robertphillips@google.com466310d2013-12-03 16:43:54 +00002035 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002036 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002037 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002038 (fIsVolatile << kIsVolatile_SerializationShift) |
2039 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002040
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002041 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002042 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002043
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002044 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002045
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002046 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002047 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048}
2049
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002050size_t SkPath::readFromMemory(const void* storage, size_t length) {
2051 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002052
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002053 int32_t packed;
2054 if (!buffer.readS32(&packed)) {
2055 return 0;
2056 }
2057
reed8f086022015-06-11 14:22:19 -07002058 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002059 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2060 return 0;
2061 }
mtklein1b249332015-07-07 12:21:21 -07002062
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002064 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002065 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002066 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002067 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002068 if (!pathRef) {
2069 return 0;
2070 }
2071
2072 fPathRef.reset(pathRef);
2073 SkDEBUGCODE(this->validate();)
2074 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002075
reed8f086022015-06-11 14:22:19 -07002076 // compatibility check
2077 if (version < kPathPrivFirstDirection_Version) {
2078 switch (dir) { // old values
2079 case 0:
2080 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2081 break;
2082 case 1:
2083 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2084 break;
2085 case 2:
2086 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2087 break;
2088 default:
2089 SkASSERT(false);
2090 }
2091 } else {
2092 fFirstDirection = dir;
2093 }
2094
ajumaf8aec582016-01-13 13:46:31 -08002095 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096}
2097
2098///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099
reede05fed02014-12-15 07:59:53 -08002100#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002101#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002102
reed@google.com51bbe752013-01-17 22:07:50 +00002103static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002104 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002105 str->append(label);
2106 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002107
reed@google.com51bbe752013-01-17 22:07:50 +00002108 const SkScalar* values = &pts[0].fX;
2109 count *= 2;
2110
2111 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002112 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002113 if (i < count - 1) {
2114 str->append(", ");
2115 }
2116 }
reed@google.com277c3f82013-05-31 15:17:50 +00002117 if (conicWeight >= 0) {
2118 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002119 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002120 }
caryclark08fa28c2014-10-23 13:08:56 -07002121 str->append(");");
reede05fed02014-12-15 07:59:53 -08002122 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002123 str->append(" // ");
2124 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002125 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002126 if (i < count - 1) {
2127 str->append(", ");
2128 }
2129 }
2130 if (conicWeight >= 0) {
2131 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002132 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002133 }
2134 }
2135 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002136}
2137
caryclarke9562592014-09-15 09:26:09 -07002138void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002139 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 Iter iter(*this, forceClose);
2141 SkPoint pts[4];
2142 Verb verb;
2143
caryclark66a5d8b2014-06-24 08:30:15 -07002144 if (!wStream) {
2145 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2146 }
reed@google.com51bbe752013-01-17 22:07:50 +00002147 SkString builder;
2148
reed@google.com4a3b7142012-05-16 17:16:46 +00002149 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 switch (verb) {
2151 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002152 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002155 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
2157 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002158 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002160 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002161 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002162 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002164 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 break;
2166 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002167 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 break;
2169 default:
2170 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2171 verb = kDone_Verb; // stop the loop
2172 break;
2173 }
caryclark1049f122015-04-20 08:31:59 -07002174 if (!wStream && builder.size()) {
2175 SkDebugf("%s", builder.c_str());
2176 builder.reset();
2177 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002178 }
caryclark66a5d8b2014-06-24 08:30:15 -07002179 if (wStream) {
2180 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002181 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182}
2183
reed@android.come522ca52009-11-23 20:10:41 +00002184void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002185 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002186}
2187
2188void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002189 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002190}
2191
2192#ifdef SK_DEBUG
2193void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002194 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002195
djsollen@google.com077348c2012-10-22 20:23:32 +00002196#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002197 if (!fBoundsIsDirty) {
2198 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002199
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002200 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002201 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002202
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002203 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002204 // if we're empty, fBounds may be empty but translated, so we can't
2205 // necessarily compare to bounds directly
2206 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2207 // be [2, 2, 2, 2]
2208 SkASSERT(bounds.isEmpty());
2209 SkASSERT(fBounds.isEmpty());
2210 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002211 if (bounds.isEmpty()) {
2212 SkASSERT(fBounds.isEmpty());
2213 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002214 if (!fBounds.isEmpty()) {
2215 SkASSERT(fBounds.contains(bounds));
2216 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002217 }
reed@android.come522ca52009-11-23 20:10:41 +00002218 }
2219 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002220#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002221}
djsollen@google.com077348c2012-10-22 20:23:32 +00002222#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002223
reed@google.com04863fa2011-05-15 04:08:24 +00002224///////////////////////////////////////////////////////////////////////////////
2225
reed@google.com0b7b9822011-05-16 12:29:27 +00002226static int sign(SkScalar x) { return x < 0; }
2227#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002228
robertphillipsc506e302014-09-16 09:43:31 -07002229enum DirChange {
2230 kLeft_DirChange,
2231 kRight_DirChange,
2232 kStraight_DirChange,
2233 kBackwards_DirChange,
2234
2235 kInvalid_DirChange
2236};
2237
2238
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002239static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002240 // The error epsilon was empirically derived; worse case round rects
2241 // with a mid point outset by 2x float epsilon in tests had an error
2242 // of 12.
2243 const int epsilon = 16;
2244 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2245 return false;
2246 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002247 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002248 int aBits = SkFloatAs2sCompliment(compA);
2249 int bBits = SkFloatAs2sCompliment(compB);
2250 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002251}
2252
caryclarkb4216502015-03-02 13:02:34 -08002253static bool approximately_zero_when_compared_to(double x, double y) {
2254 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002255}
2256
caryclarkb4216502015-03-02 13:02:34 -08002257
reed@google.com04863fa2011-05-15 04:08:24 +00002258// only valid for a single contour
2259struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002260 Convexicator()
2261 : fPtCount(0)
2262 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002263 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002264 , fIsFinite(true)
2265 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002266 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 // warnings
djsollen2f124632016-04-29 13:53:05 -07002268 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002269 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002270 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002271 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002272 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002273
2274 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002275 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002276 }
2277
2278 SkPath::Convexity getConvexity() const { return fConvexity; }
2279
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002281 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282
reed@google.com04863fa2011-05-15 04:08:24 +00002283 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002284 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002285 return;
2286 }
2287
2288 if (0 == fPtCount) {
2289 fCurrPt = pt;
2290 ++fPtCount;
2291 } else {
2292 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002293 SkScalar lengthSqd = vec.lengthSqd();
2294 if (!SkScalarIsFinite(lengthSqd)) {
2295 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002296 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002297 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002298 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 fCurrPt = pt;
2300 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002301 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002302 } else {
2303 SkASSERT(fPtCount > 2);
2304 this->addVec(vec);
2305 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002306
reed@google.com85b6e392011-05-15 20:25:17 +00002307 int sx = sign(vec.fX);
2308 int sy = sign(vec.fY);
2309 fDx += (sx != fSx);
2310 fDy += (sy != fSy);
2311 fSx = sx;
2312 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002313
reed@google.com85b6e392011-05-15 20:25:17 +00002314 if (fDx > 3 || fDy > 3) {
2315 fConvexity = SkPath::kConcave_Convexity;
2316 }
reed@google.com04863fa2011-05-15 04:08:24 +00002317 }
2318 }
2319 }
2320
2321 void close() {
2322 if (fPtCount > 2) {
2323 this->addVec(fFirstVec);
2324 }
2325 }
2326
caryclarkb4216502015-03-02 13:02:34 -08002327 DirChange directionChange(const SkVector& curVec) {
2328 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2329
2330 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2331 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2332 largest = SkTMax(largest, -smallest);
2333
2334 if (!almost_equal(largest, largest + cross)) {
2335 int sign = SkScalarSignAsInt(cross);
2336 if (sign) {
2337 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2338 }
2339 }
2340
2341 if (cross) {
2342 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2343 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2344 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2345 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2346 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2347 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2348 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2349 if (sign) {
2350 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2351 }
2352 }
2353 }
2354
2355 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2356 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2357 fLastVec.dot(curVec) < 0.0f) {
2358 return kBackwards_DirChange;
2359 }
2360
2361 return kStraight_DirChange;
2362 }
2363
2364
caryclarkd3d1a982014-12-08 04:57:38 -08002365 bool isFinite() const {
2366 return fIsFinite;
2367 }
2368
caryclark5ccef572015-03-02 10:07:56 -08002369 void setCurve(bool isCurve) {
2370 fIsCurve = isCurve;
2371 }
2372
reed@google.com04863fa2011-05-15 04:08:24 +00002373private:
2374 void addVec(const SkVector& vec) {
2375 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002376 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002377 switch (dir) {
2378 case kLeft_DirChange: // fall through
2379 case kRight_DirChange:
2380 if (kInvalid_DirChange == fExpectedDir) {
2381 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002382 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2383 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002384 } else if (dir != fExpectedDir) {
2385 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002386 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002387 }
2388 fLastVec = vec;
2389 break;
2390 case kStraight_DirChange:
2391 break;
2392 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002393 if (fIsCurve) {
2394 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002395 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002396 }
robertphillipsc506e302014-09-16 09:43:31 -07002397 fLastVec = vec;
2398 break;
2399 case kInvalid_DirChange:
2400 SkFAIL("Use of invalid direction change flag");
2401 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 }
2403 }
2404
caryclarkb4216502015-03-02 13:02:34 -08002405 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002406 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002408 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2409 // value with the current vec is deemed to be of a significant value.
2410 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002411 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002412 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002413 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002414 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002415 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002416 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002417 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002418};
2419
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420SkPath::Convexity SkPath::internalGetConvexity() const {
2421 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002422 SkPoint pts[4];
2423 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002424 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002425
2426 int contourCount = 0;
2427 int count;
2428 Convexicator state;
2429
caryclarkd3d1a982014-12-08 04:57:38 -08002430 if (!isFinite()) {
2431 return kUnknown_Convexity;
2432 }
caryclarke8c56662015-07-14 11:19:26 -07002433 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002434 switch (verb) {
2435 case kMove_Verb:
2436 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002437 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002438 return kConcave_Convexity;
2439 }
2440 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002441 // fall through
2442 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002443 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002444 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002445 break;
caryclark5ccef572015-03-02 10:07:56 -08002446 case kQuad_Verb:
2447 // fall through
2448 case kConic_Verb:
2449 // fall through
2450 case kCubic_Verb:
2451 count = 2 + (kCubic_Verb == verb);
2452 // As an additional enhancement, this could set curve true only
2453 // if the curve is nonlinear
2454 state.setCurve(true);
2455 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002456 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002457 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002458 state.close();
2459 count = 0;
2460 break;
2461 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002462 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002463 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002464 return kConcave_Convexity;
2465 }
2466
2467 for (int i = 1; i <= count; i++) {
2468 state.addPt(pts[i]);
2469 }
2470 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002471 if (!state.isFinite()) {
2472 return kUnknown_Convexity;
2473 }
reed@google.com04863fa2011-05-15 04:08:24 +00002474 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002475 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002476 return kConcave_Convexity;
2477 }
2478 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002479 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002480 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2481 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002482 }
2483 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002484}
reed@google.com69a99432012-01-10 18:00:10 +00002485
2486///////////////////////////////////////////////////////////////////////////////
2487
2488class ContourIter {
2489public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002490 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002491
2492 bool done() const { return fDone; }
2493 // if !done() then these may be called
2494 int count() const { return fCurrPtCount; }
2495 const SkPoint* pts() const { return fCurrPt; }
2496 void next();
2497
2498private:
2499 int fCurrPtCount;
2500 const SkPoint* fCurrPt;
2501 const uint8_t* fCurrVerb;
2502 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002503 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002504 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002505 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002506};
2507
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002508ContourIter::ContourIter(const SkPathRef& pathRef) {
2509 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002510 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002511 fCurrPt = pathRef.points();
2512 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002513 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002514 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002515 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002516 this->next();
2517}
2518
2519void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002520 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002521 fDone = true;
2522 }
2523 if (fDone) {
2524 return;
2525 }
2526
2527 // skip pts of prev contour
2528 fCurrPt += fCurrPtCount;
2529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002531 int ptCount = 1; // moveTo
2532 const uint8_t* verbs = fCurrVerb;
2533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002534 for (--verbs; verbs > fStopVerbs; --verbs) {
2535 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002536 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002537 goto CONTOUR_END;
2538 case SkPath::kLine_Verb:
2539 ptCount += 1;
2540 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002541 case SkPath::kConic_Verb:
2542 fCurrConicWeight += 1;
2543 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002544 case SkPath::kQuad_Verb:
2545 ptCount += 2;
2546 break;
2547 case SkPath::kCubic_Verb:
2548 ptCount += 3;
2549 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002550 case SkPath::kClose_Verb:
2551 break;
2552 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002553 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002554 break;
2555 }
2556 }
2557CONTOUR_END:
2558 fCurrPtCount = ptCount;
2559 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002560 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002561}
2562
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002563// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002564static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002565 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2566 // We may get 0 when the above subtracts underflow. We expect this to be
2567 // very rare and lazily promote to double.
2568 if (0 == cross) {
2569 double p0x = SkScalarToDouble(p0.fX);
2570 double p0y = SkScalarToDouble(p0.fY);
2571
2572 double p1x = SkScalarToDouble(p1.fX);
2573 double p1y = SkScalarToDouble(p1.fY);
2574
2575 double p2x = SkScalarToDouble(p2.fX);
2576 double p2y = SkScalarToDouble(p2.fY);
2577
2578 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2579 (p1y - p0y) * (p2x - p0x));
2580
2581 }
2582 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002583}
2584
reed@google.comc1ea60a2012-01-31 15:15:36 +00002585// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002586static int find_max_y(const SkPoint pts[], int count) {
2587 SkASSERT(count > 0);
2588 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002589 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002590 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002591 SkScalar y = pts[i].fY;
2592 if (y > max) {
2593 max = y;
2594 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002595 }
2596 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002597 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002598}
2599
reed@google.comcabaf1d2012-01-11 21:03:05 +00002600static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2601 int i = index;
2602 for (;;) {
2603 i = (i + inc) % n;
2604 if (i == index) { // we wrapped around, so abort
2605 break;
2606 }
2607 if (pts[index] != pts[i]) { // found a different point, success!
2608 break;
2609 }
2610 }
2611 return i;
2612}
2613
reed@google.comc1ea60a2012-01-31 15:15:36 +00002614/**
2615 * Starting at index, and moving forward (incrementing), find the xmin and
2616 * xmax of the contiguous points that have the same Y.
2617 */
2618static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2619 int* maxIndexPtr) {
2620 const SkScalar y = pts[index].fY;
2621 SkScalar min = pts[index].fX;
2622 SkScalar max = min;
2623 int minIndex = index;
2624 int maxIndex = index;
2625 for (int i = index + 1; i < n; ++i) {
2626 if (pts[i].fY != y) {
2627 break;
2628 }
2629 SkScalar x = pts[i].fX;
2630 if (x < min) {
2631 min = x;
2632 minIndex = i;
2633 } else if (x > max) {
2634 max = x;
2635 maxIndex = i;
2636 }
2637 }
2638 *maxIndexPtr = maxIndex;
2639 return minIndex;
2640}
2641
reed026beb52015-06-10 14:23:15 -07002642static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2643 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002644}
2645
reed@google.comac8543f2012-01-30 20:51:25 +00002646/*
2647 * We loop through all contours, and keep the computed cross-product of the
2648 * contour that contained the global y-max. If we just look at the first
2649 * contour, we may find one that is wound the opposite way (correctly) since
2650 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2651 * that is outer most (or at least has the global y-max) before we can consider
2652 * its cross product.
2653 */
reed026beb52015-06-10 14:23:15 -07002654bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002655 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2656 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002657 return true;
2658 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002659
2660 // don't want to pay the cost for computing this if it
2661 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002662 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2663 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002664 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002665 return false;
2666 }
reed@google.com69a99432012-01-10 18:00:10 +00002667
reed026beb52015-06-10 14:23:15 -07002668 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002669
reed@google.comac8543f2012-01-30 20:51:25 +00002670 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002671 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002672 SkScalar ymaxCross = 0;
2673
reed@google.com69a99432012-01-10 18:00:10 +00002674 for (; !iter.done(); iter.next()) {
2675 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002676 if (n < 3) {
2677 continue;
2678 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002679
reed@google.comcabaf1d2012-01-11 21:03:05 +00002680 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002681 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002682 int index = find_max_y(pts, n);
2683 if (pts[index].fY < ymax) {
2684 continue;
2685 }
2686
2687 // If there is more than 1 distinct point at the y-max, we take the
2688 // x-min and x-max of them and just subtract to compute the dir.
2689 if (pts[(index + 1) % n].fY == pts[index].fY) {
2690 int maxIndex;
2691 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2692 if (minIndex == maxIndex) {
2693 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002694 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002695 SkASSERT(pts[minIndex].fY == pts[index].fY);
2696 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2697 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2698 // we just subtract the indices, and let that auto-convert to
2699 // SkScalar, since we just want - or + to signal the direction.
2700 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002701 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002702 TRY_CROSSPROD:
2703 // Find a next and prev index to use for the cross-product test,
2704 // but we try to find pts that form non-zero vectors from pts[index]
2705 //
2706 // Its possible that we can't find two non-degenerate vectors, so
2707 // we have to guard our search (e.g. all the pts could be in the
2708 // same place).
2709
2710 // we pass n - 1 instead of -1 so we don't foul up % operator by
2711 // passing it a negative LH argument.
2712 int prev = find_diff_pt(pts, index, n, n - 1);
2713 if (prev == index) {
2714 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002715 continue;
2716 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002717 int next = find_diff_pt(pts, index, n, 1);
2718 SkASSERT(next != index);
2719 cross = cross_prod(pts[prev], pts[index], pts[next]);
2720 // if we get a zero and the points are horizontal, then we look at the spread in
2721 // x-direction. We really should continue to walk away from the degeneracy until
2722 // there is a divergence.
2723 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2724 // construct the subtract so we get the correct Direction below
2725 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002726 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002727 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002728
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002729 if (cross) {
2730 // record our best guess so far
2731 ymax = pts[index].fY;
2732 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002733 }
2734 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002735 if (ymaxCross) {
2736 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002737 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002738 return true;
2739 } else {
2740 return false;
2741 }
reed@google.comac8543f2012-01-30 20:51:25 +00002742}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002743
2744///////////////////////////////////////////////////////////////////////////////
2745
caryclark9aacd902015-12-14 08:38:09 -08002746static bool between(SkScalar a, SkScalar b, SkScalar c) {
2747 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2748 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2749 return (a - b) * (c - b) <= 0;
2750}
2751
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2753 SkScalar D, SkScalar t) {
2754 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2755}
2756
2757static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2758 SkScalar t) {
2759 SkScalar A = c3 + 3*(c1 - c2) - c0;
2760 SkScalar B = 3*(c2 - c1 - c1 + c0);
2761 SkScalar C = 3*(c1 - c0);
2762 SkScalar D = c0;
2763 return eval_cubic_coeff(A, B, C, D, t);
2764}
2765
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002766template <size_t N> static void find_minmax(const SkPoint pts[],
2767 SkScalar* minPtr, SkScalar* maxPtr) {
2768 SkScalar min, max;
2769 min = max = pts[0].fX;
2770 for (size_t i = 1; i < N; ++i) {
2771 min = SkMinScalar(min, pts[i].fX);
2772 max = SkMaxScalar(max, pts[i].fX);
2773 }
2774 *minPtr = min;
2775 *maxPtr = max;
2776}
2777
caryclark9cb5d752015-12-18 04:35:24 -08002778static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2779 if (start.fY == end.fY) {
2780 return between(start.fX, x, end.fX) && x != end.fX;
2781 } else {
2782 return x == start.fX && y == start.fY;
2783 }
2784}
2785
caryclark9aacd902015-12-14 08:38:09 -08002786static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002787 SkScalar y0 = pts[0].fY;
2788 SkScalar y3 = pts[3].fY;
2789
2790 int dir = 1;
2791 if (y0 > y3) {
2792 SkTSwap(y0, y3);
2793 dir = -1;
2794 }
2795 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002796 return 0;
2797 }
caryclark9cb5d752015-12-18 04:35:24 -08002798 if (checkOnCurve(x, y, pts[0], pts[3])) {
2799 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002800 return 0;
2801 }
caryclark9cb5d752015-12-18 04:35:24 -08002802 if (y == y3) {
2803 return 0;
2804 }
2805
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 // quickreject or quickaccept
2807 SkScalar min, max;
2808 find_minmax<4>(pts, &min, &max);
2809 if (x < min) {
2810 return 0;
2811 }
2812 if (x > max) {
2813 return dir;
2814 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002817 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002818 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002819 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002820 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002821 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002822 if (SkScalarNearlyEqual(xt, x)) {
2823 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2824 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002825 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002826 }
2827 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 return xt < x ? dir : 0;
2829}
2830
caryclark9aacd902015-12-14 08:38:09 -08002831static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002832 SkPoint dst[10];
2833 int n = SkChopCubicAtYExtrema(pts, dst);
2834 int w = 0;
2835 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002836 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 }
2838 return w;
2839}
2840
caryclark9aacd902015-12-14 08:38:09 -08002841static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2842 SkASSERT(src);
2843 SkASSERT(t >= 0 && t <= 1);
2844 SkScalar src2w = src[2] * w;
2845 SkScalar C = src[0];
2846 SkScalar A = src[4] - 2 * src2w + C;
2847 SkScalar B = 2 * (src2w - C);
2848 return (A * t + B) * t + C;
2849}
2850
2851
2852static double conic_eval_denominator(SkScalar w, SkScalar t) {
2853 SkScalar B = 2 * (w - 1);
2854 SkScalar C = 1;
2855 SkScalar A = -B;
2856 return (A * t + B) * t + C;
2857}
2858
2859static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2860 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 SkScalar y0 = pts[0].fY;
2862 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 int dir = 1;
2865 if (y0 > y2) {
2866 SkTSwap(y0, y2);
2867 dir = -1;
2868 }
caryclark9aacd902015-12-14 08:38:09 -08002869 if (y < y0 || y > y2) {
2870 return 0;
2871 }
caryclark9cb5d752015-12-18 04:35:24 -08002872 if (checkOnCurve(x, y, pts[0], pts[2])) {
2873 *onCurveCount += 1;
2874 return 0;
2875 }
caryclark9aacd902015-12-14 08:38:09 -08002876 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 return 0;
2878 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
caryclark9aacd902015-12-14 08:38:09 -08002880 SkScalar roots[2];
2881 SkScalar A = pts[2].fY;
2882 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2883 SkScalar C = pts[0].fY;
2884 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2885 B -= C; // B = b*w - w * yCept + yCept - a
2886 C -= y;
2887 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2888 SkASSERT(n <= 1);
2889 SkScalar xt;
2890 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002891 // zero roots are returned only when y0 == y
2892 // Need [0] if dir == 1
2893 // and [2] if dir == -1
2894 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002895 } else {
2896 SkScalar t = roots[0];
2897 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2898 }
2899 if (SkScalarNearlyEqual(xt, x)) {
2900 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2901 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002902 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002903 }
2904 }
2905 return xt < x ? dir : 0;
2906}
2907
2908static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2909 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2910 if (y0 == y1) {
2911 return true;
2912 }
2913 if (y0 < y1) {
2914 return y1 <= y2;
2915 } else {
2916 return y1 >= y2;
2917 }
2918}
2919
2920static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2921 int* onCurveCount) {
2922 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002923 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002924 // If the data points are very large, the conic may not be monotonic but may also
2925 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002926 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2927 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2928 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002929 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2930 }
2931 return w;
2932}
2933
2934static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2935 SkScalar y0 = pts[0].fY;
2936 SkScalar y2 = pts[2].fY;
2937
2938 int dir = 1;
2939 if (y0 > y2) {
2940 SkTSwap(y0, y2);
2941 dir = -1;
2942 }
2943 if (y < y0 || y > y2) {
2944 return 0;
2945 }
caryclark9cb5d752015-12-18 04:35:24 -08002946 if (checkOnCurve(x, y, pts[0], pts[2])) {
2947 *onCurveCount += 1;
2948 return 0;
2949 }
caryclark9aacd902015-12-14 08:38:09 -08002950 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002951 return 0;
2952 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002953 // bounds check on X (not required. is it faster?)
2954#if 0
2955 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2956 return 0;
2957 }
2958#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002959
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 SkScalar roots[2];
2961 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2962 2 * (pts[1].fY - pts[0].fY),
2963 pts[0].fY - y,
2964 roots);
2965 SkASSERT(n <= 1);
2966 SkScalar xt;
2967 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002968 // zero roots are returned only when y0 == y
2969 // Need [0] if dir == 1
2970 // and [2] if dir == -1
2971 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 } else {
2973 SkScalar t = roots[0];
2974 SkScalar C = pts[0].fX;
2975 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2976 SkScalar B = 2 * (pts[1].fX - C);
2977 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2978 }
caryclark9aacd902015-12-14 08:38:09 -08002979 if (SkScalarNearlyEqual(xt, x)) {
2980 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2981 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002982 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002983 }
2984 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 return xt < x ? dir : 0;
2986}
2987
caryclark9aacd902015-12-14 08:38:09 -08002988static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 SkPoint dst[5];
2990 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002991
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2993 n = SkChopQuadAtYExtrema(pts, dst);
2994 pts = dst;
2995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002998 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 }
3000 return w;
3001}
3002
caryclark9aacd902015-12-14 08:38:09 -08003003static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 SkScalar x0 = pts[0].fX;
3005 SkScalar y0 = pts[0].fY;
3006 SkScalar x1 = pts[1].fX;
3007 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003008
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003010
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 int dir = 1;
3012 if (y0 > y1) {
3013 SkTSwap(y0, y1);
3014 dir = -1;
3015 }
caryclark9aacd902015-12-14 08:38:09 -08003016 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003017 return 0;
3018 }
caryclark9cb5d752015-12-18 04:35:24 -08003019 if (checkOnCurve(x, y, pts[0], pts[1])) {
3020 *onCurveCount += 1;
3021 return 0;
3022 }
3023 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003024 return 0;
3025 }
3026 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003027
caryclark9aacd902015-12-14 08:38:09 -08003028 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003029 // zero cross means the point is on the line, and since the case where
3030 // y of the query point is at the end point is handled above, we can be
3031 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003032 if (x != x1 || y != pts[1].fY) {
3033 *onCurveCount += 1;
3034 }
caryclark9aacd902015-12-14 08:38:09 -08003035 dir = 0;
3036 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 dir = 0;
3038 }
3039 return dir;
3040}
3041
caryclark9aacd902015-12-14 08:38:09 -08003042static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3043 SkTDArray<SkVector>* tangents) {
3044 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3045 && !between(pts[2].fY, y, pts[3].fY)) {
3046 return;
3047 }
3048 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3049 && !between(pts[2].fX, x, pts[3].fX)) {
3050 return;
3051 }
3052 SkPoint dst[10];
3053 int n = SkChopCubicAtYExtrema(pts, dst);
3054 for (int i = 0; i <= n; ++i) {
3055 SkPoint* c = &dst[i * 3];
3056 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003057 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003058 continue;
mbarbella276e6332016-05-31 14:44:01 -07003059 }
caryclark9aacd902015-12-14 08:38:09 -08003060 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3061 if (!SkScalarNearlyEqual(x, xt)) {
3062 continue;
3063 }
3064 SkVector tangent;
3065 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3066 tangents->push(tangent);
3067 }
3068}
3069
3070static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3071 SkTDArray<SkVector>* tangents) {
3072 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3073 return;
3074 }
3075 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3076 return;
3077 }
3078 SkScalar roots[2];
3079 SkScalar A = pts[2].fY;
3080 SkScalar B = pts[1].fY * w - y * w + y;
3081 SkScalar C = pts[0].fY;
3082 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3083 B -= C; // B = b*w - w * yCept + yCept - a
3084 C -= y;
3085 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3086 for (int index = 0; index < n; ++index) {
3087 SkScalar t = roots[index];
3088 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3089 if (!SkScalarNearlyEqual(x, xt)) {
3090 continue;
3091 }
3092 SkConic conic(pts, w);
3093 tangents->push(conic.evalTangentAt(t));
3094 }
3095}
3096
3097static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3098 SkTDArray<SkVector>* tangents) {
3099 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3100 return;
3101 }
3102 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3103 return;
3104 }
3105 SkScalar roots[2];
3106 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3107 2 * (pts[1].fY - pts[0].fY),
3108 pts[0].fY - y,
3109 roots);
3110 for (int index = 0; index < n; ++index) {
3111 SkScalar t = roots[index];
3112 SkScalar C = pts[0].fX;
3113 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3114 SkScalar B = 2 * (pts[1].fX - C);
3115 SkScalar xt = (A * t + B) * t + C;
3116 if (!SkScalarNearlyEqual(x, xt)) {
3117 continue;
3118 }
3119 tangents->push(SkEvalQuadTangentAt(pts, t));
3120 }
3121}
3122
3123static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3124 SkTDArray<SkVector>* tangents) {
3125 SkScalar y0 = pts[0].fY;
3126 SkScalar y1 = pts[1].fY;
3127 if (!between(y0, y, y1)) {
3128 return;
3129 }
3130 SkScalar x0 = pts[0].fX;
3131 SkScalar x1 = pts[1].fX;
3132 if (!between(x0, x, x1)) {
3133 return;
3134 }
3135 SkScalar dx = x1 - x0;
3136 SkScalar dy = y1 - y0;
3137 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3138 return;
3139 }
3140 SkVector v;
3141 v.set(dx, dy);
3142 tangents->push(v);
3143}
3144
reed@google.com4db592c2013-10-30 17:39:43 +00003145static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3146 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3147}
3148
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003149bool SkPath::contains(SkScalar x, SkScalar y) const {
3150 bool isInverse = this->isInverseFillType();
3151 if (this->isEmpty()) {
3152 return isInverse;
3153 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003154
reed@google.com4db592c2013-10-30 17:39:43 +00003155 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003156 return isInverse;
3157 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003158
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 SkPath::Iter iter(*this, true);
3160 bool done = false;
3161 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003162 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163 do {
3164 SkPoint pts[4];
3165 switch (iter.next(pts, false)) {
3166 case SkPath::kMove_Verb:
3167 case SkPath::kClose_Verb:
3168 break;
3169 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003170 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 break;
3172 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003173 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003175 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003176 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003177 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003178 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003179 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 break;
3181 case SkPath::kDone_Verb:
3182 done = true;
3183 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003184 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003185 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003186 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3187 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3188 if (evenOddFill) {
3189 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003190 }
caryclark9aacd902015-12-14 08:38:09 -08003191 if (w) {
3192 return !isInverse;
3193 }
3194 if (onCurveCount <= 1) {
3195 return SkToBool(onCurveCount) ^ isInverse;
3196 }
3197 if ((onCurveCount & 1) || evenOddFill) {
3198 return SkToBool(onCurveCount & 1) ^ isInverse;
3199 }
halcanary9d524f22016-03-29 09:03:52 -07003200 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003201 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3202 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003203 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003204 SkTDArray<SkVector> tangents;
3205 do {
3206 SkPoint pts[4];
3207 int oldCount = tangents.count();
3208 switch (iter.next(pts, false)) {
3209 case SkPath::kMove_Verb:
3210 case SkPath::kClose_Verb:
3211 break;
3212 case SkPath::kLine_Verb:
3213 tangent_line(pts, x, y, &tangents);
3214 break;
3215 case SkPath::kQuad_Verb:
3216 tangent_quad(pts, x, y, &tangents);
3217 break;
3218 case SkPath::kConic_Verb:
3219 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3220 break;
3221 case SkPath::kCubic_Verb:
3222 tangent_cubic(pts, x, y, &tangents);
3223 break;
3224 case SkPath::kDone_Verb:
3225 done = true;
3226 break;
3227 }
3228 if (tangents.count() > oldCount) {
3229 int last = tangents.count() - 1;
3230 const SkVector& tangent = tangents[last];
3231 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3232 tangents.remove(last);
3233 } else {
3234 for (int index = 0; index < last; ++index) {
3235 const SkVector& test = tangents[index];
3236 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003237 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3238 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003239 tangents.remove(last);
3240 tangents.removeShuffle(index);
3241 break;
3242 }
3243 }
3244 }
3245 }
3246 } while (!done);
3247 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003248}
fmalitaaa0df4e2015-12-01 09:13:23 -08003249
3250int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3251 SkScalar w, SkPoint pts[], int pow2) {
3252 const SkConic conic(p0, p1, p2, w);
3253 return conic.chopIntoQuadsPOW2(pts, pow2);
3254}
bsalomonedc743a2016-06-01 09:42:31 -07003255
3256bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3257 unsigned* start) {
3258 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3259 return false;
3260 }
3261 SkPath::RawIter iter(path);
3262 SkPoint verbPts[4];
3263 SkPath::Verb v;
3264 SkPoint rectPts[5];
3265 int rectPtCnt = 0;
3266 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3267 switch (v) {
3268 case SkPath::kMove_Verb:
3269 if (0 != rectPtCnt) {
3270 return false;
3271 }
3272 rectPts[0] = verbPts[0];
3273 ++rectPtCnt;
3274 break;
3275 case SkPath::kLine_Verb:
3276 if (5 == rectPtCnt) {
3277 return false;
3278 }
3279 rectPts[rectPtCnt] = verbPts[1];
3280 ++rectPtCnt;
3281 break;
3282 case SkPath::kClose_Verb:
3283 if (4 == rectPtCnt) {
3284 rectPts[4] = rectPts[0];
3285 rectPtCnt = 5;
3286 }
3287 break;
3288 default:
3289 return false;
3290 }
3291 }
3292 if (rectPtCnt < 5) {
3293 return false;
3294 }
3295 if (rectPts[0] != rectPts[4]) {
3296 return false;
3297 }
bsalomon057ae8a2016-07-24 05:37:26 -07003298 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3299 // and pts 1 and 2 the opposite vertical or horizontal edge).
3300 bool vec03IsVertical;
3301 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3302 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3303 // Make sure it has non-zero width and height
3304 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003305 return false;
3306 }
bsalomon057ae8a2016-07-24 05:37:26 -07003307 vec03IsVertical = true;
3308 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3309 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3310 // Make sure it has non-zero width and height
3311 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3312 return false;
3313 }
3314 vec03IsVertical = false;
3315 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003316 return false;
3317 }
bsalomon057ae8a2016-07-24 05:37:26 -07003318 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3319 // set if it is on the bottom edge.
3320 unsigned sortFlags =
3321 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3322 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3323 switch (sortFlags) {
3324 case 0b00:
3325 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3326 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3327 *start = 0;
3328 break;
3329 case 0b01:
3330 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3331 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3332 *start = 1;
3333 break;
3334 case 0b10:
3335 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3336 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3337 *start = 3;
3338 break;
3339 case 0b11:
3340 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3341 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3342 *start = 2;
3343 break;
bsalomonedc743a2016-06-01 09:42:31 -07003344 }
bsalomonedc743a2016-06-01 09:42:31 -07003345 return true;
3346}
bsalomon21af9ca2016-08-25 12:29:23 -07003347
3348void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3349 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3350 SkASSERT(!oval.isEmpty());
3351 SkASSERT(sweepAngle);
3352
3353 path->reset();
3354 path->setIsVolatile(true);
3355 path->setFillType(SkPath::kWinding_FillType);
3356 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3357 path->addOval(oval);
3358 return;
3359 }
3360 if (useCenter) {
3361 path->moveTo(oval.centerX(), oval.centerY());
3362 }
3363 // Arc to mods at 360 and drawArc is not supposed to.
3364 bool forceMoveTo = !useCenter;
3365 while (sweepAngle <= -360.f) {
3366 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3367 startAngle -= 180.f;
3368 path->arcTo(oval, startAngle, -180.f, false);
3369 startAngle -= 180.f;
3370 forceMoveTo = false;
3371 sweepAngle += 360.f;
3372 }
3373 while (sweepAngle >= 360.f) {
3374 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3375 startAngle += 180.f;
3376 path->arcTo(oval, startAngle, 180.f, false);
3377 startAngle += 180.f;
3378 forceMoveTo = false;
3379 sweepAngle -= 360.f;
3380 }
3381 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3382 if (useCenter) {
3383 path->close();
3384 }
3385}