blob: 8da15804deb69b8defca2c8fb0dbf6f5a64be835 [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
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070012#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
16////////////////////////////////////////////////////////////////////////////
17
reed@google.com3563c9e2011-11-14 19:34:57 +000018/**
19 * Path.bounds is defined to be the bounds of all the control points.
20 * If we called bounds.join(r) we would skip r if r was empty, which breaks
21 * our promise. Hence we have a custom joiner that doesn't look at emptiness
22 */
23static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
24 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
25 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
26 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
27 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
28}
29
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000030static bool is_degenerate(const SkPath& path) {
31 SkPath::Iter iter(path, false);
32 SkPoint pts[4];
33 return SkPath::kDone_Verb == iter.next(pts);
34}
35
bsalomon@google.com30c174b2012-11-13 14:36:42 +000036class SkAutoDisableDirectionCheck {
37public:
38 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
reed026beb52015-06-10 14:23:15 -070039 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +000040 }
41
42 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070043 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000044 }
45
46private:
reed026beb52015-06-10 14:23:15 -070047 SkPath* fPath;
48 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000050#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000051
reed@android.com8a1c16f2008-12-17 15:59:43 +000052/* This guy's constructor/destructor bracket a path editing operation. It is
53 used when we know the bounds of the amount we are going to add to the path
54 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000055
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000057 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000058 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000059
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000060 It also notes if the path was originally degenerate, and if so, sets
61 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000062 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000063 */
64class SkAutoPathBoundsUpdate {
65public:
66 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
67 this->init(path);
68 }
69
70 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
71 SkScalar right, SkScalar bottom) {
72 fRect.set(left, top, right, bottom);
73 this->init(path);
74 }
reed@google.comabf15c12011-01-18 20:35:51 +000075
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000077 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
78 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000079 if (fEmpty || fHasValidBounds) {
80 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 }
82 }
reed@google.comabf15c12011-01-18 20:35:51 +000083
reed@android.com8a1c16f2008-12-17 15:59:43 +000084private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000085 SkPath* fPath;
86 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000087 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000088 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000089 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000090
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000092 // Cannot use fRect for our bounds unless we know it is sorted
93 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000095 // Mark the path's bounds as dirty if (1) they are, or (2) the path
96 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000097 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000098 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 if (fHasValidBounds && !fEmpty) {
100 joinNoEmptyChecks(&fRect, fPath->getBounds());
101 }
102 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 }
104};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000105#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107////////////////////////////////////////////////////////////////////////////
108
109/*
110 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000111 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
113
114 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000115 1. If we encounter degenerate segments, remove them
116 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
117 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
118 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119*/
120
121////////////////////////////////////////////////////////////////////////////
122
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000123// flag to require a moveTo if we begin with something else, like lineTo etc.
124#define INITIAL_LASTMOVETOINDEX_VALUE ~0
125
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000126SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800127 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000128 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700129 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130}
131
132void SkPath::resetFields() {
133 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000134 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000136 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700137 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000138
139 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700140 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000141}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142
bungeman@google.coma5809a32013-06-21 15:13:34 +0000143SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000144 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145 this->copyFields(that);
146 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147}
148
149SkPath::~SkPath() {
150 SkDEBUGCODE(this->validate();)
151}
152
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153SkPath& SkPath::operator=(const SkPath& that) {
154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 if (this != &that) {
157 fPathRef.reset(SkRef(that.fPathRef.get()));
158 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 }
160 SkDEBUGCODE(this->validate();)
161 return *this;
162}
163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164void SkPath::copyFields(const SkPath& that) {
165 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000166 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fConvexity = that.fConvexity;
reed026beb52015-06-10 14:23:15 -0700169 fFirstDirection = that.fFirstDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700170 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000171}
172
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000173bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000174 // note: don't need to look at isConvex or bounds, since just comparing the
175 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000176 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000177 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000178}
179
bungeman@google.coma5809a32013-06-21 15:13:34 +0000180void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 if (this != &that) {
182 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000183 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
reed026beb52015-06-10 14:23:15 -0700186 SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700187 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188 }
189}
190
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000191static inline bool check_edge_against_rect(const SkPoint& p0,
192 const SkPoint& p1,
193 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700194 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000195 const SkPoint* edgeBegin;
196 SkVector v;
reed026beb52015-06-10 14:23:15 -0700197 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000198 v = p1 - p0;
199 edgeBegin = &p0;
200 } else {
201 v = p0 - p1;
202 edgeBegin = &p1;
203 }
204 if (v.fX || v.fY) {
205 // check the cross product of v with the vec from edgeBegin to each rect corner
206 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
207 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
208 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
209 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
210 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
211 return false;
212 }
213 }
214 return true;
215}
216
217bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
218 // This only handles non-degenerate convex paths currently.
219 if (kConvex_Convexity != this->getConvexity()) {
220 return false;
221 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000222
reed026beb52015-06-10 14:23:15 -0700223 SkPathPriv::FirstDirection direction;
224 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000225 return false;
226 }
227
228 SkPoint firstPt;
229 SkPoint prevPt;
230 RawIter iter(*this);
231 SkPath::Verb verb;
232 SkPoint pts[4];
233 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000234 SkDEBUGCODE(int segmentCount = 0;)
235 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000236
237 while ((verb = iter.next(pts)) != kDone_Verb) {
238 int nextPt = -1;
239 switch (verb) {
240 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000241 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000242 SkDEBUGCODE(++moveCnt);
243 firstPt = prevPt = pts[0];
244 break;
245 case kLine_Verb:
246 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000247 SkASSERT(moveCnt && !closeCount);
248 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000249 break;
250 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000251 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkASSERT(moveCnt && !closeCount);
253 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 nextPt = 2;
255 break;
256 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(moveCnt && !closeCount);
258 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 nextPt = 3;
260 break;
261 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 break;
264 default:
265 SkDEBUGFAIL("unknown verb");
266 }
267 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800268 if (SkPath::kConic_Verb == verb) {
269 SkConic orig;
270 orig.set(pts, iter.conicWeight());
271 SkPoint quadPts[5];
272 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
273 SK_ALWAYSBREAK(2 == count);
274
275 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
276 return false;
277 }
278 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
279 return false;
280 }
281 } else {
282 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
283 return false;
284 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285 }
286 prevPt = pts[nextPt];
287 }
288 }
289
290 return check_edge_against_rect(prevPt, firstPt, rect, direction);
291}
292
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000293uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000294 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800295#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000296 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
297 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
298#endif
299 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000300}
djsollen@google.come63793a2012-03-21 15:39:03 +0000301
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302void SkPath::reset() {
303 SkDEBUGCODE(this->validate();)
304
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000305 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000306 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307}
308
309void SkPath::rewind() {
310 SkDEBUGCODE(this->validate();)
311
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000312 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000313 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314}
315
reed@google.com7e6c4d12012-05-10 14:05:43 +0000316bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000318
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000319 if (2 == verbCount) {
320 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
321 if (kLine_Verb == fPathRef->atVerb(1)) {
322 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000323 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000324 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000325 line[0] = pts[0];
326 line[1] = pts[1];
327 }
328 return true;
329 }
330 }
331 return false;
332}
333
caryclark@google.comf1316942011-07-26 19:54:45 +0000334/*
335 Determines if path is a rect by keeping track of changes in direction
336 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000337
caryclark@google.comf1316942011-07-26 19:54:45 +0000338 The direction is computed such that:
339 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000340 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000341 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000342 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000343
caryclark@google.comf1316942011-07-26 19:54:45 +0000344A rectangle cycles up/right/down/left or up/left/down/right.
345
346The test fails if:
347 The path is closed, and followed by a line.
348 A second move creates a new endpoint.
349 A diagonal line is parsed.
350 There's more than four changes of direction.
351 There's a discontinuity on the line (e.g., a move in the middle)
352 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000353 The path contains a quadratic or cubic.
354 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000355 *The rectangle doesn't complete a cycle.
356 *The final point isn't equal to the first point.
357
358 *These last two conditions we relax if we have a 3-edge path that would
359 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000360
caryclark@google.comf1316942011-07-26 19:54:45 +0000361It's OK if the path has:
362 Several colinear line segments composing a rectangle side.
363 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000364
caryclark@google.comf1316942011-07-26 19:54:45 +0000365The direction takes advantage of the corners found since opposite sides
366must travel in opposite directions.
367
368FIXME: Allow colinear quads and cubics to be treated like lines.
369FIXME: If the API passes fill-only, return true if the filled stroke
370 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000371
372 first,last,next direction state-machine:
373 0x1 is set if the segment is horizontal
374 0x2 is set if the segment is moving to the right or down
375 thus:
376 two directions are opposites iff (dirA ^ dirB) == 0x2
377 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000380static int rect_make_dir(SkScalar dx, SkScalar dy) {
381 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
382}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
384 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000385 int corners = 0;
386 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000387 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700388 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000389 first.set(0, 0);
390 last.set(0, 0);
391 int firstDirection = 0;
392 int lastDirection = 0;
393 int nextDirection = 0;
394 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000395 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700396 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000397 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000398 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700399 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
400 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000401 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000402 savePts = pts;
403 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700405 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 case kLine_Verb: {
407 SkScalar left = last.fX;
408 SkScalar top = last.fY;
409 SkScalar right = pts->fX;
410 SkScalar bottom = pts->fY;
411 ++pts;
412 if (left != right && top != bottom) {
413 return false; // diagonal
414 }
415 if (left == right && top == bottom) {
416 break; // single point on side OK
417 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000418 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 if (0 == corners) {
420 firstDirection = nextDirection;
421 first = last;
422 last = pts[-1];
423 corners = 1;
424 closedOrMoved = false;
425 break;
426 }
427 if (closedOrMoved) {
428 return false; // closed followed by a line
429 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000430 if (autoClose && nextDirection == firstDirection) {
431 break; // colinear with first
432 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000433 closedOrMoved = autoClose;
434 if (lastDirection != nextDirection) {
435 if (++corners > 4) {
436 return false; // too many direction changes
437 }
438 }
439 last = pts[-1];
440 if (lastDirection == nextDirection) {
441 break; // colinear segment
442 }
443 // Possible values for corners are 2, 3, and 4.
444 // When corners == 3, nextDirection opposes firstDirection.
445 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000446 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
448 if ((directionCycle ^ turn) != nextDirection) {
449 return false; // direction didn't follow cycle
450 }
451 break;
452 }
453 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000454 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 case kCubic_Verb:
456 return false; // quadratic, cubic not allowed
457 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700458 if (allowPartial && !autoClose && firstDirection) {
459 insertClose = true;
460 *currVerb -= 1; // try move again afterwards
461 goto addMissingClose;
462 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 last = *pts++;
464 closedOrMoved = true;
465 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000466 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000467 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000468 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000470 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700472addMissingClose:
473 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 }
475 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000476 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000477 if (!result) {
478 // check if we are just an incomplete rectangle, in which case we can
479 // return true, but not claim to be closed.
480 // e.g.
481 // 3 sided rectangle
482 // 4 sided but the last edge is not long enough to reach the start
483 //
484 SkScalar closeX = first.x() - last.x();
485 SkScalar closeY = first.y() - last.y();
486 if (closeX && closeY) {
487 return false; // we're diagonal, abort (can we ever reach this?)
488 }
489 int closeDirection = rect_make_dir(closeX, closeY);
490 // make sure the close-segment doesn't double-back on itself
491 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
492 result = true;
493 autoClose = false; // we are not closed
494 }
495 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000496 if (savePts) {
497 *ptsPtr = savePts;
498 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000499 if (result && isClosed) {
500 *isClosed = autoClose;
501 }
502 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000503 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000504 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000505 return result;
506}
507
robertphillips4f662e62014-12-29 14:06:51 -0800508bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000509 SkDEBUGCODE(this->validate();)
510 int currVerb = 0;
511 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800512 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800513 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800514 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000515 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800516 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800517 int32_t num = SkToS32(pts - first);
518 if (num) {
519 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800520 } else {
521 // 'pts' isn't updated for open rects
522 *rect = this->getBounds();
523 }
524 }
525 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000526}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000527
caryclark95bc5f32015-04-08 08:34:15 -0700528bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000529 SkDEBUGCODE(this->validate();)
530 int currVerb = 0;
531 const SkPoint* pts = fPathRef->points();
532 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000533 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700534 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000535 return false;
536 }
537 const SkPoint* last = pts;
538 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700539 bool isClosed;
540 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000541 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700542 if (!isClosed) {
543 pts = fPathRef->points() + fPathRef->countPoints();
544 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000546 if (testRects[0].contains(testRects[1])) {
547 if (rects) {
548 rects[0] = testRects[0];
549 rects[1] = testRects[1];
550 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000551 if (dirs) {
552 dirs[0] = testDirs[0];
553 dirs[1] = testDirs[1];
554 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000555 return true;
556 }
557 if (testRects[1].contains(testRects[0])) {
558 if (rects) {
559 rects[0] = testRects[1];
560 rects[1] = testRects[0];
561 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000562 if (dirs) {
563 dirs[0] = testDirs[1];
564 dirs[1] = testDirs[0];
565 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000566 return true;
567 }
568 }
569 return false;
570}
571
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000572int SkPath::countPoints() const {
573 return fPathRef->countPoints();
574}
575
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000576int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 SkDEBUGCODE(this->validate();)
578
579 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000580 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000581 int count = SkMin32(max, fPathRef->countPoints());
582 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
583 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584}
585
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000586SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000587 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
588 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000589 }
590 return SkPoint::Make(0, 0);
591}
592
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000593int SkPath::countVerbs() const {
594 return fPathRef->countVerbs();
595}
596
597static inline void copy_verbs_reverse(uint8_t* inorderDst,
598 const uint8_t* reversedSrc,
599 int count) {
600 for (int i = 0; i < count; ++i) {
601 inorderDst[i] = reversedSrc[~i];
602 }
603}
604
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000605int SkPath::getVerbs(uint8_t dst[], int max) const {
606 SkDEBUGCODE(this->validate();)
607
608 SkASSERT(max >= 0);
609 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610 int count = SkMin32(max, fPathRef->countVerbs());
611 copy_verbs_reverse(dst, fPathRef->verbs(), count);
612 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000613}
614
reed@google.com294dd7b2011-10-11 11:58:32 +0000615bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 SkDEBUGCODE(this->validate();)
617
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000618 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000619 if (count > 0) {
620 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000621 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000623 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000625 if (lastPt) {
626 lastPt->set(0, 0);
627 }
628 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629}
630
caryclarkaec25102015-04-29 08:28:30 -0700631void SkPath::setPt(int index, SkScalar x, SkScalar y) {
632 SkDEBUGCODE(this->validate();)
633
634 int count = fPathRef->countPoints();
635 if (count <= index) {
636 return;
637 } else {
638 SkPathRef::Editor ed(&fPathRef);
639 ed.atPoint(index)->set(x, y);
640 }
641}
642
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643void SkPath::setLastPt(SkScalar x, SkScalar y) {
644 SkDEBUGCODE(this->validate();)
645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 if (count == 0) {
648 this->moveTo(x, y);
649 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 SkPathRef::Editor ed(&fPathRef);
651 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653}
654
reed@google.com04863fa2011-05-15 04:08:24 +0000655void SkPath::setConvexity(Convexity c) {
656 if (fConvexity != c) {
657 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000658 }
659}
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661//////////////////////////////////////////////////////////////////////////////
662// Construction methods
663
reed026beb52015-06-10 14:23:15 -0700664#define DIRTY_AFTER_EDIT \
665 do { \
666 fConvexity = kUnknown_Convexity; \
667 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000668 } while (0)
669
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670void SkPath::incReserve(U16CPU inc) {
671 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 SkDEBUGCODE(this->validate();)
674}
675
676void SkPath::moveTo(SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000681 // remember our index
682 fLastMoveToIndex = fPathRef->countPoints();
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700685
686 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687}
688
689void SkPath::rMoveTo(SkScalar x, SkScalar y) {
690 SkPoint pt;
691 this->getLastPt(&pt);
692 this->moveTo(pt.fX + x, pt.fY + y);
693}
694
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000695void SkPath::injectMoveToIfNeeded() {
696 if (fLastMoveToIndex < 0) {
697 SkScalar x, y;
698 if (fPathRef->countVerbs() == 0) {
699 x = y = 0;
700 } else {
701 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
702 x = pt.fX;
703 y = pt.fY;
704 }
705 this->moveTo(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::lineTo(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000712 this->injectMoveToIfNeeded();
713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716
reed@google.comb54455e2011-05-16 14:16:04 +0000717 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718}
719
720void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000721 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 SkPoint pt;
723 this->getLastPt(&pt);
724 this->lineTo(pt.fX + x, pt.fY + y);
725}
726
727void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
728 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000729
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000730 this->injectMoveToIfNeeded();
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
733 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 pts[0].set(x1, y1);
735 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000736
reed@google.comb54455e2011-05-16 14:16:04 +0000737 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738}
739
740void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000741 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 SkPoint pt;
743 this->getLastPt(&pt);
744 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
745}
746
reed@google.com277c3f82013-05-31 15:17:50 +0000747void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
748 SkScalar w) {
749 // check for <= 0 or NaN with this test
750 if (!(w > 0)) {
751 this->lineTo(x2, y2);
752 } else if (!SkScalarIsFinite(w)) {
753 this->lineTo(x1, y1);
754 this->lineTo(x2, y2);
755 } else if (SK_Scalar1 == w) {
756 this->quadTo(x1, y1, x2, y2);
757 } else {
758 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000759
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000760 this->injectMoveToIfNeeded();
761
reed@google.com277c3f82013-05-31 15:17:50 +0000762 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000763 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000766
reed@google.com277c3f82013-05-31 15:17:50 +0000767 DIRTY_AFTER_EDIT;
768 }
769}
770
771void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
772 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
777}
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 pts[0].set(x1, y1);
788 pts[1].set(x2, y2);
789 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790
reed@google.comb54455e2011-05-16 14:16:04 +0000791 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792}
793
794void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
795 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000796 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkPoint pt;
798 this->getLastPt(&pt);
799 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
800 pt.fX + x3, pt.fY + y3);
801}
802
803void SkPath::close() {
804 SkDEBUGCODE(this->validate();)
805
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000806 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 case kLine_Verb:
810 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000811 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 case kMove_Verb: {
814 SkPathRef::Editor ed(&fPathRef);
815 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 }
reed@google.com277c3f82013-05-31 15:17:50 +0000818 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000819 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000820 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000821 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000822 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000823 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 }
825 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826
827 // signal that we need a moveTo to follow us (unless we're done)
828#if 0
829 if (fLastMoveToIndex >= 0) {
830 fLastMoveToIndex = ~fLastMoveToIndex;
831 }
832#else
833 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
834#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835}
836
837///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000838
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000839static void assert_known_direction(int dir) {
840 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
841}
842
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843void SkPath::addRect(const SkRect& rect, Direction dir) {
844 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
845}
846
847void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
848 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000849 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700850 fFirstDirection = this->hasOnlyMoveTos() ?
851 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000852 SkAutoDisableDirectionCheck addc(this);
853
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
855
856 this->incReserve(5);
857
858 this->moveTo(left, top);
859 if (dir == kCCW_Direction) {
860 this->lineTo(left, bottom);
861 this->lineTo(right, bottom);
862 this->lineTo(right, top);
863 } else {
864 this->lineTo(right, top);
865 this->lineTo(right, bottom);
866 this->lineTo(left, bottom);
867 }
868 this->close();
869}
870
reed@google.com744faba2012-05-29 19:54:52 +0000871void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
872 SkDEBUGCODE(this->validate();)
873 if (count <= 0) {
874 return;
875 }
876
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000877 fLastMoveToIndex = fPathRef->countPoints();
878
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000879 // +close makes room for the extra kClose_Verb
880 SkPathRef::Editor ed(&fPathRef, count+close, count);
881
882 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000883 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000884 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
885 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000886 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000887
reed@google.com744faba2012-05-29 19:54:52 +0000888 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000889 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800890 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000891 }
892
reed@google.com744faba2012-05-29 19:54:52 +0000893 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000894 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000895}
896
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000897#include "SkGeometry.h"
898
reedf90ea012015-01-29 12:03:58 -0800899static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
900 SkPoint* pt) {
901 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000902 // Chrome uses this path to move into and out of ovals. If not
903 // treated as a special case the moves can distort the oval's
904 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800905 pt->set(oval.fRight, oval.centerY());
906 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000907 } else if (0 == oval.width() && 0 == oval.height()) {
908 // Chrome will sometimes create 0 radius round rects. Having degenerate
909 // quad segments in the path prevents the path from being recognized as
910 // a rect.
911 // TODO: optimizing the case where only one of width or height is zero
912 // should also be considered. This case, however, doesn't seem to be
913 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800914 pt->set(oval.fRight, oval.fTop);
915 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000916 }
reedf90ea012015-01-29 12:03:58 -0800917 return false;
918}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000919
reedd5d27d92015-02-09 13:54:43 -0800920// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
921//
922static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
923 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
924 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
925 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000926
927 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800928 loss in radians-conversion and/or sin/cos, we may end up with coincident
929 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
930 of drawing a nearly complete circle (good).
931 e.g. canvas.drawArc(0, 359.99, ...)
932 -vs- canvas.drawArc(0, 359.9, ...)
933 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000934 */
reedd5d27d92015-02-09 13:54:43 -0800935 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000936 SkScalar sw = SkScalarAbs(sweepAngle);
937 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
938 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
939 // make a guess at a tiny angle (in radians) to tweak by
940 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
941 // not sure how much will be enough, so we use a loop
942 do {
943 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800944 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
945 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000946 }
947 }
reedd5d27d92015-02-09 13:54:43 -0800948 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
949}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000950
reed9e779d42015-02-17 11:43:14 -0800951/**
952 * If this returns 0, then the caller should just line-to the singlePt, else it should
953 * ignore singlePt and append the specified number of conics.
954 */
reedd5d27d92015-02-09 13:54:43 -0800955static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800956 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
957 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800958 SkMatrix matrix;
959
960 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
961 matrix.postTranslate(oval.centerX(), oval.centerY());
962
reed9e779d42015-02-17 11:43:14 -0800963 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
964 if (0 == count) {
965 matrix.mapXY(start.x(), start.y(), singlePt);
966 }
967 return count;
reedd5d27d92015-02-09 13:54:43 -0800968}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000969
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000970void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000971 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000972 SkRRect rrect;
973 rrect.setRectRadii(rect, (const SkVector*) radii);
974 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975}
976
reed@google.com4ed0fb72012-12-12 20:48:18 +0000977void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000978 assert_known_direction(dir);
979
980 if (rrect.isEmpty()) {
981 return;
982 }
983
reed@google.com4ed0fb72012-12-12 20:48:18 +0000984 const SkRect& bounds = rrect.getBounds();
985
986 if (rrect.isRect()) {
987 this->addRect(bounds, dir);
988 } else if (rrect.isOval()) {
989 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000990 } else {
reed026beb52015-06-10 14:23:15 -0700991 fFirstDirection = this->hasOnlyMoveTos() ?
992 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000993
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000994 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000995 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000996
reed1b28a3a2014-12-17 14:42:09 -0800997 const SkScalar L = bounds.fLeft;
998 const SkScalar T = bounds.fTop;
999 const SkScalar R = bounds.fRight;
1000 const SkScalar B = bounds.fBottom;
1001 const SkScalar W = SK_ScalarRoot2Over2;
1002
1003 this->incReserve(13);
1004 if (kCW_Direction == dir) {
1005 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1006
1007 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1008 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1009
1010 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1011 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1012
1013 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1014 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1015
1016 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1017 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1018 } else {
1019 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1020
1021 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1022 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1023
1024 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1025 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1026
1027 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1028 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1029
1030 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1031 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1032 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001033 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001034 }
reed5bcbe912014-12-15 12:28:33 -08001035 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001036}
1037
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001038bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001039 int count = fPathRef->countVerbs();
1040 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1041 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001042 if (*verbs == kLine_Verb ||
1043 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001044 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001045 *verbs == kCubic_Verb) {
1046 return false;
1047 }
1048 ++verbs;
1049 }
1050 return true;
1051}
1052
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001053void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1054 Direction dir) {
1055 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001056
humper@google.com75e3ca12013-04-08 21:44:11 +00001057 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001058 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001059 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001060 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001061 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1062 return;
1063 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001064
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001065 SkRRect rrect;
1066 rrect.setRectXY(rect, rx, ry);
1067 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001068}
1069
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001071 assert_known_direction(dir);
1072
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001073 /* If addOval() is called after previous moveTo(),
1074 this path is still marked as an oval. This is used to
1075 fit into WebKit's calling sequences.
1076 We can't simply check isEmpty() in this case, as additional
1077 moveTo() would mark the path non empty.
1078 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001079 bool isOval = hasOnlyMoveTos();
1080 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001081 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001082 } else {
reed026beb52015-06-10 14:23:15 -07001083 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001084 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001085
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001086 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001087
reed@android.com8a1c16f2008-12-17 15:59:43 +00001088 SkAutoPathBoundsUpdate apbu(this, oval);
1089
reed220f9262014-12-17 08:21:04 -08001090 const SkScalar L = oval.fLeft;
1091 const SkScalar T = oval.fTop;
1092 const SkScalar R = oval.fRight;
1093 const SkScalar B = oval.fBottom;
1094 const SkScalar cx = oval.centerX();
1095 const SkScalar cy = oval.centerY();
1096 const SkScalar weight = SK_ScalarRoot2Over2;
1097
1098 this->incReserve(9); // move + 4 conics
1099 this->moveTo(R, cy);
1100 if (dir == kCCW_Direction) {
1101 this->conicTo(R, T, cx, T, weight);
1102 this->conicTo(L, T, L, cy, weight);
1103 this->conicTo(L, B, cx, B, weight);
1104 this->conicTo(R, B, R, cy, weight);
1105 } else {
1106 this->conicTo(R, B, cx, B, weight);
1107 this->conicTo(L, B, L, cy, weight);
1108 this->conicTo(L, T, cx, T, weight);
1109 this->conicTo(R, T, R, cy, weight);
1110 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112
robertphillips@google.com466310d2013-12-03 16:43:54 +00001113 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001114
robertphillips@google.com466310d2013-12-03 16:43:54 +00001115 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001116}
1117
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1119 if (r > 0) {
1120 SkRect rect;
1121 rect.set(x - r, y - r, x + r, y + r);
1122 this->addOval(rect, dir);
1123 }
1124}
1125
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1127 bool forceMoveTo) {
1128 if (oval.width() < 0 || oval.height() < 0) {
1129 return;
1130 }
1131
reedf90ea012015-01-29 12:03:58 -08001132 if (fPathRef->countVerbs() == 0) {
1133 forceMoveTo = true;
1134 }
1135
1136 SkPoint lonePt;
1137 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1138 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1139 return;
1140 }
1141
reedd5d27d92015-02-09 13:54:43 -08001142 SkVector startV, stopV;
1143 SkRotationDirection dir;
1144 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1145
reed9e779d42015-02-17 11:43:14 -08001146 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001147 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001148 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001149 if (count) {
1150 this->incReserve(count * 2 + 1);
1151 const SkPoint& pt = conics[0].fPts[0];
1152 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1153 for (int i = 0; i < count; ++i) {
1154 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1155 }
reed9e779d42015-02-17 11:43:14 -08001156 } else {
1157 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001158 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159}
1160
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001161void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 if (oval.isEmpty() || 0 == sweepAngle) {
1163 return;
1164 }
1165
1166 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1167
1168 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1169 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001170 } else {
1171 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173}
1174
1175/*
1176 Need to handle the case when the angle is sharp, and our computed end-points
1177 for the arc go behind pt1 and/or p2...
1178*/
reedc7789042015-01-29 12:59:11 -08001179void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001180 if (radius == 0) {
1181 this->lineTo(x1, y1);
1182 return;
1183 }
1184
1185 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001186
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 // need to know our prev pt so we can construct tangent vectors
1188 {
1189 SkPoint start;
1190 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001191 // Handle degenerate cases by adding a line to the first point and
1192 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 before.setNormalize(x1 - start.fX, y1 - start.fY);
1194 after.setNormalize(x2 - x1, y2 - y1);
1195 }
reed@google.comabf15c12011-01-18 20:35:51 +00001196
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 SkScalar cosh = SkPoint::DotProduct(before, after);
1198 SkScalar sinh = SkPoint::CrossProduct(before, after);
1199
1200 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001201 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 return;
1203 }
reed@google.comabf15c12011-01-18 20:35:51 +00001204
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1206 if (dist < 0) {
1207 dist = -dist;
1208 }
1209
1210 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1211 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1212 SkRotationDirection arcDir;
1213
1214 // now turn before/after into normals
1215 if (sinh > 0) {
1216 before.rotateCCW();
1217 after.rotateCCW();
1218 arcDir = kCW_SkRotationDirection;
1219 } else {
1220 before.rotateCW();
1221 after.rotateCW();
1222 arcDir = kCCW_SkRotationDirection;
1223 }
1224
1225 SkMatrix matrix;
1226 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001227
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 matrix.setScale(radius, radius);
1229 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1230 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 this->incReserve(count);
1235 // [xx,yy] == pts[0]
1236 this->lineTo(xx, yy);
1237 for (int i = 1; i < count; i += 2) {
1238 this->quadTo(pts[i], pts[i+1]);
1239 }
1240}
1241
1242///////////////////////////////////////////////////////////////////////////////
1243
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001244void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 SkMatrix matrix;
1246
1247 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001248 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249}
1250
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001251void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001252 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001254 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 SkPoint pts[4];
1256 Verb verb;
1257
1258 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001259 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 while ((verb = iter.next(pts)) != kDone_Verb) {
1261 switch (verb) {
1262 case kMove_Verb:
1263 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001264 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1265 injectMoveToIfNeeded(); // In case last contour is closed
1266 this->lineTo(pts[0]);
1267 } else {
1268 this->moveTo(pts[0]);
1269 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 break;
1271 case kLine_Verb:
1272 proc(matrix, &pts[1], &pts[1], 1);
1273 this->lineTo(pts[1]);
1274 break;
1275 case kQuad_Verb:
1276 proc(matrix, &pts[1], &pts[1], 2);
1277 this->quadTo(pts[1], pts[2]);
1278 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001279 case kConic_Verb:
1280 proc(matrix, &pts[1], &pts[1], 2);
1281 this->conicTo(pts[1], pts[2], iter.conicWeight());
1282 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 case kCubic_Verb:
1284 proc(matrix, &pts[1], &pts[1], 3);
1285 this->cubicTo(pts[1], pts[2], pts[3]);
1286 break;
1287 case kClose_Verb:
1288 this->close();
1289 break;
1290 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001291 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001293 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 }
1295}
1296
1297///////////////////////////////////////////////////////////////////////////////
1298
reed@google.com277c3f82013-05-31 15:17:50 +00001299static int pts_in_verb(unsigned verb) {
1300 static const uint8_t gPtsInVerb[] = {
1301 1, // kMove
1302 1, // kLine
1303 2, // kQuad
1304 2, // kConic
1305 3, // kCubic
1306 0, // kClose
1307 0 // kDone
1308 };
1309
1310 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1311 return gPtsInVerb[verb];
1312}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314// ignore the last point of the 1st contour
1315void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001316 int i, vcount = path.fPathRef->countVerbs();
1317 // exit early if the path is empty, or just has a moveTo.
1318 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 return;
1320 }
1321
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001322 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001324 const uint8_t* verbs = path.fPathRef->verbs();
1325 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001326 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001328 SkASSERT(verbs[~0] == kMove_Verb);
1329 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001330 unsigned v = verbs[~i];
1331 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 if (n == 0) {
1333 break;
1334 }
1335 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001336 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 }
1338
1339 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001340 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 case kLine_Verb:
1342 this->lineTo(pts[-1].fX, pts[-1].fY);
1343 break;
1344 case kQuad_Verb:
1345 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1346 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001347 case kConic_Verb:
1348 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1349 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 case kCubic_Verb:
1351 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1352 pts[-3].fX, pts[-3].fY);
1353 break;
1354 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001355 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 break;
1357 }
reed@google.com277c3f82013-05-31 15:17:50 +00001358 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 }
1360}
1361
reed@google.com63d73742012-01-10 15:33:12 +00001362void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001363 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001364
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001365 const SkPoint* pts = src.fPathRef->pointsEnd();
1366 // we will iterator through src's verbs backwards
1367 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1368 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001369 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001370
1371 bool needMove = true;
1372 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001373 while (verbs < verbsEnd) {
1374 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001375 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001376
1377 if (needMove) {
1378 --pts;
1379 this->moveTo(pts->fX, pts->fY);
1380 needMove = false;
1381 }
1382 pts -= n;
1383 switch (v) {
1384 case kMove_Verb:
1385 if (needClose) {
1386 this->close();
1387 needClose = false;
1388 }
1389 needMove = true;
1390 pts += 1; // so we see the point in "if (needMove)" above
1391 break;
1392 case kLine_Verb:
1393 this->lineTo(pts[0]);
1394 break;
1395 case kQuad_Verb:
1396 this->quadTo(pts[1], pts[0]);
1397 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001398 case kConic_Verb:
1399 this->conicTo(pts[1], pts[0], *--conicWeights);
1400 break;
reed@google.com63d73742012-01-10 15:33:12 +00001401 case kCubic_Verb:
1402 this->cubicTo(pts[2], pts[1], pts[0]);
1403 break;
1404 case kClose_Verb:
1405 needClose = true;
1406 break;
1407 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001408 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001409 }
1410 }
1411}
1412
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413///////////////////////////////////////////////////////////////////////////////
1414
1415void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1416 SkMatrix matrix;
1417
1418 matrix.setTranslate(dx, dy);
1419 this->transform(matrix, dst);
1420}
1421
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1423 int level = 2) {
1424 if (--level >= 0) {
1425 SkPoint tmp[7];
1426
1427 SkChopCubicAtHalf(pts, tmp);
1428 subdivide_cubic_to(path, &tmp[0], level);
1429 subdivide_cubic_to(path, &tmp[3], level);
1430 } else {
1431 path->cubicTo(pts[1], pts[2], pts[3]);
1432 }
1433}
1434
1435void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1436 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001437 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 dst = (SkPath*)this;
1439 }
1440
tomhudson@google.com8d430182011-06-06 19:11:19 +00001441 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 SkPath tmp;
1443 tmp.fFillType = fFillType;
1444
1445 SkPath::Iter iter(*this, false);
1446 SkPoint pts[4];
1447 SkPath::Verb verb;
1448
reed@google.com4a3b7142012-05-16 17:16:46 +00001449 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 switch (verb) {
1451 case kMove_Verb:
1452 tmp.moveTo(pts[0]);
1453 break;
1454 case kLine_Verb:
1455 tmp.lineTo(pts[1]);
1456 break;
1457 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001458 // promote the quad to a conic
1459 tmp.conicTo(pts[1], pts[2],
1460 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001462 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001463 tmp.conicTo(pts[1], pts[2],
1464 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001465 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 case kCubic_Verb:
1467 subdivide_cubic_to(&tmp, pts);
1468 break;
1469 case kClose_Verb:
1470 tmp.close();
1471 break;
1472 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001473 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 break;
1475 }
1476 }
1477
1478 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001479 SkPathRef::Editor ed(&dst->fPathRef);
1480 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001481 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1484
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001487 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001488 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001490
reed026beb52015-06-10 14:23:15 -07001491 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1492 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001493 } else {
1494 SkScalar det2x2 =
1495 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1496 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1497 if (det2x2 < 0) {
reed026beb52015-06-10 14:23:15 -07001498 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection((SkPathPriv::FirstDirection)fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001499 } else if (det2x2 > 0) {
reed026beb52015-06-10 14:23:15 -07001500 dst->fFirstDirection = fFirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001501 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001502 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001503 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001504 }
1505 }
1506
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 SkDEBUGCODE(dst->validate();)
1508 }
1509}
1510
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511///////////////////////////////////////////////////////////////////////////////
1512///////////////////////////////////////////////////////////////////////////////
1513
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001514enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001515 kEmptyContour_SegmentState, // The current contour is empty. We may be
1516 // starting processing or we may have just
1517 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001518 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1519 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1520 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521};
1522
1523SkPath::Iter::Iter() {
1524#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001525 fPts = nullptr;
1526 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001528 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001529 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530#endif
1531 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001532 fVerbs = nullptr;
1533 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 fNeedClose = false;
1535}
1536
1537SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1538 this->setPath(path, forceClose);
1539}
1540
1541void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 fPts = path.fPathRef->points();
1543 fVerbs = path.fPathRef->verbs();
1544 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001545 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001546 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001547 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 fForceClose = SkToU8(forceClose);
1549 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001550 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551}
1552
1553bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001554 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 return false;
1556 }
1557 if (fForceClose) {
1558 return true;
1559 }
1560
1561 const uint8_t* verbs = fVerbs;
1562 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001564 if (kMove_Verb == *(verbs - 1)) {
1565 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566 }
1567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 while (verbs > stop) {
1569 // verbs points one beyond the current verb, decrement first.
1570 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 if (kMove_Verb == v) {
1572 break;
1573 }
1574 if (kClose_Verb == v) {
1575 return true;
1576 }
1577 }
1578 return false;
1579}
1580
1581SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001582 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001584 // A special case: if both points are NaN, SkPoint::operation== returns
1585 // false, but the iterator expects that they are treated as the same.
1586 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001587 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1588 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001589 return kClose_Verb;
1590 }
1591
reed@google.com9e25dbf2012-05-15 17:05:38 +00001592 pts[0] = fLastPt;
1593 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 fLastPt = fMoveTo;
1595 fCloseLine = true;
1596 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001597 } else {
1598 pts[0] = fMoveTo;
1599 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601}
1602
reed@google.com9e25dbf2012-05-15 17:05:38 +00001603const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001604 if (fSegmentState == kAfterMove_SegmentState) {
1605 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001606 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001607 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001609 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1610 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001611 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613}
1614
caryclarke8c56662015-07-14 11:19:26 -07001615void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001616 // We need to step over anything that will not move the current draw point
1617 // forward before the next move is seen
1618 const uint8_t* lastMoveVerb = 0;
1619 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001620 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001621 SkPoint lastPt = fLastPt;
1622 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001623 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001624 switch (verb) {
1625 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001626 // Keep a record of this most recent move
1627 lastMoveVerb = fVerbs;
1628 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001629 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001630 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001632 fPts++;
1633 break;
1634
1635 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001636 // A close when we are in a segment is always valid except when it
1637 // follows a move which follows a segment.
1638 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001639 return;
1640 }
1641 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001642 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001643 break;
1644
1645 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001646 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001647 if (lastMoveVerb) {
1648 fVerbs = lastMoveVerb;
1649 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001650 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001651 return;
1652 }
1653 return;
1654 }
1655 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001656 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001657 fPts++;
1658 break;
1659
reed@google.com277c3f82013-05-31 15:17:50 +00001660 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001661 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001662 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001663 if (lastMoveVerb) {
1664 fVerbs = lastMoveVerb;
1665 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001666 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001667 return;
1668 }
1669 return;
1670 }
1671 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001673 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001674 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001675 break;
1676
1677 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001678 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001679 if (lastMoveVerb) {
1680 fVerbs = lastMoveVerb;
1681 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001682 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001683 return;
1684 }
1685 return;
1686 }
1687 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001689 fPts += 3;
1690 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001691
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001692 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001693 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001694 }
1695 }
1696}
1697
reed@google.com4a3b7142012-05-16 17:16:46 +00001698SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001699 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001700
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001702 // Close the curve if requested and if there is some curve to close
1703 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001704 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 return kLine_Verb;
1706 }
1707 fNeedClose = false;
1708 return kClose_Verb;
1709 }
1710 return kDone_Verb;
1711 }
1712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001713 // fVerbs is one beyond the current verb, decrement first
1714 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001715 const SkPoint* SK_RESTRICT srcPts = fPts;
1716 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717
1718 switch (verb) {
1719 case kMove_Verb:
1720 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001721 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 verb = this->autoClose(pts);
1723 if (verb == kClose_Verb) {
1724 fNeedClose = false;
1725 }
1726 return (Verb)verb;
1727 }
1728 if (fVerbs == fVerbStop) { // might be a trailing moveto
1729 return kDone_Verb;
1730 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001731 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001732 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001734 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 fNeedClose = fForceClose;
1737 break;
1738 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001739 pts[0] = this->cons_moveTo();
1740 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 fLastPt = srcPts[0];
1742 fCloseLine = false;
1743 srcPts += 1;
1744 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001745 case kConic_Verb:
1746 fConicWeights += 1;
1747 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001749 pts[0] = this->cons_moveTo();
1750 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 fLastPt = srcPts[1];
1752 srcPts += 2;
1753 break;
1754 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001755 pts[0] = this->cons_moveTo();
1756 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757 fLastPt = srcPts[2];
1758 srcPts += 3;
1759 break;
1760 case kClose_Verb:
1761 verb = this->autoClose(pts);
1762 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764 } else {
1765 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001766 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001768 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 break;
1770 }
1771 fPts = srcPts;
1772 return (Verb)verb;
1773}
1774
1775///////////////////////////////////////////////////////////////////////////////
1776
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001777SkPath::RawIter::RawIter() {
1778#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001779 fPts = nullptr;
1780 fConicWeights = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001781#endif
1782 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001783 fVerbs = nullptr;
1784 fVerbStop = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001785}
1786
1787SkPath::RawIter::RawIter(const SkPath& path) {
1788 this->setPath(path);
1789}
1790
1791void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 fPts = path.fPathRef->points();
1793 fVerbs = path.fPathRef->verbs();
1794 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001795 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001796}
1797
1798SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001799 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001800 if (fVerbs == fVerbStop) {
1801 return kDone_Verb;
1802 }
1803
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001804 // fVerbs points one beyond next verb so decrement first.
1805 unsigned verb = *(--fVerbs);
1806 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001807
1808 switch (verb) {
1809 case kMove_Verb:
reed6e434652015-05-27 19:53:25 -07001810 pts[0] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001811 srcPts += 1;
1812 break;
1813 case kLine_Verb:
reedb5615812015-05-13 07:55:48 -07001814 pts[0] = srcPts[-1];
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001815 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001816 srcPts += 1;
1817 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001818 case kConic_Verb:
1819 fConicWeights += 1;
1820 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001821 case kQuad_Verb:
reedb5615812015-05-13 07:55:48 -07001822 pts[0] = srcPts[-1];
1823 pts[1] = srcPts[0];
1824 pts[2] = srcPts[1];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001825 srcPts += 2;
1826 break;
1827 case kCubic_Verb:
reedb5615812015-05-13 07:55:48 -07001828 pts[0] = srcPts[-1];
1829 pts[1] = srcPts[0];
1830 pts[2] = srcPts[1];
1831 pts[3] = srcPts[2];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001832 srcPts += 3;
1833 break;
1834 case kClose_Verb:
reed6e434652015-05-27 19:53:25 -07001835 break;
1836 case kDone_Verb:
1837 SkASSERT(fVerbs == fVerbStop);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001838 break;
1839 }
1840 fPts = srcPts;
1841 return (Verb)verb;
1842}
1843
1844///////////////////////////////////////////////////////////////////////////////
1845
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001847 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848*/
1849
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001850size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 SkDEBUGCODE(this->validate();)
1852
halcanary96fcdcc2015-08-27 07:41:13 -07001853 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001854 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001855 return SkAlign4(byteCount);
1856 }
1857
1858 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001859
robertphillips@google.com466310d2013-12-03 16:43:54 +00001860 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001861 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001862 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001863 (fIsVolatile << kIsVolatile_SerializationShift) |
1864 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001865
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001866 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001869
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001870 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001871 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872}
1873
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001874size_t SkPath::readFromMemory(const void* storage, size_t length) {
1875 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001876
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001877 int32_t packed;
1878 if (!buffer.readS32(&packed)) {
1879 return 0;
1880 }
1881
reed8f086022015-06-11 14:22:19 -07001882 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001883
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001884 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1885 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001886 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001887 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001888 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001889
reed8f086022015-06-11 14:22:19 -07001890 // compatibility check
1891 if (version < kPathPrivFirstDirection_Version) {
1892 switch (dir) { // old values
1893 case 0:
1894 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1895 break;
1896 case 1:
1897 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1898 break;
1899 case 2:
1900 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1901 break;
1902 default:
1903 SkASSERT(false);
1904 }
1905 } else {
1906 fFirstDirection = dir;
1907 }
1908
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001909 size_t sizeRead = 0;
1910 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001911 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001912 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001913 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001914 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001915 } else if (pathRef) {
halcanary96fcdcc2015-08-27 07:41:13 -07001916 // If the buffer is not valid, pathRef should be nullptr
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001917 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001918 }
1919 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920}
1921
1922///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923
reede05fed02014-12-15 07:59:53 -08001924#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001925#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001926
reed@google.com51bbe752013-01-17 22:07:50 +00001927static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001928 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001929 str->append(label);
1930 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001931
reed@google.com51bbe752013-01-17 22:07:50 +00001932 const SkScalar* values = &pts[0].fX;
1933 count *= 2;
1934
1935 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001936 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001937 if (i < count - 1) {
1938 str->append(", ");
1939 }
1940 }
reed@google.com277c3f82013-05-31 15:17:50 +00001941 if (conicWeight >= 0) {
1942 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001943 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001944 }
caryclark08fa28c2014-10-23 13:08:56 -07001945 str->append(");");
reede05fed02014-12-15 07:59:53 -08001946 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001947 str->append(" // ");
1948 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001949 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001950 if (i < count - 1) {
1951 str->append(", ");
1952 }
1953 }
1954 if (conicWeight >= 0) {
1955 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001956 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001957 }
1958 }
1959 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001960}
1961
caryclarke9562592014-09-15 09:26:09 -07001962void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001963 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 Iter iter(*this, forceClose);
1965 SkPoint pts[4];
1966 Verb verb;
1967
caryclark66a5d8b2014-06-24 08:30:15 -07001968 if (!wStream) {
1969 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1970 }
reed@google.com51bbe752013-01-17 22:07:50 +00001971 SkString builder;
1972
reed@google.com4a3b7142012-05-16 17:16:46 +00001973 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 switch (verb) {
1975 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001976 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 break;
1978 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001979 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 break;
1981 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001982 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001984 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001985 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001986 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001987 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001988 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989 break;
1990 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001991 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 break;
1993 default:
1994 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1995 verb = kDone_Verb; // stop the loop
1996 break;
1997 }
caryclark1049f122015-04-20 08:31:59 -07001998 if (!wStream && builder.size()) {
1999 SkDebugf("%s", builder.c_str());
2000 builder.reset();
2001 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 }
caryclark66a5d8b2014-06-24 08:30:15 -07002003 if (wStream) {
2004 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002005 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006}
2007
reed@android.come522ca52009-11-23 20:10:41 +00002008void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002009 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002010}
2011
2012void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002013 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002014}
2015
2016#ifdef SK_DEBUG
2017void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002018 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002019
djsollen@google.com077348c2012-10-22 20:23:32 +00002020#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002021 if (!fBoundsIsDirty) {
2022 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002023
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002024 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002025 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002026
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002027 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002028 // if we're empty, fBounds may be empty but translated, so we can't
2029 // necessarily compare to bounds directly
2030 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2031 // be [2, 2, 2, 2]
2032 SkASSERT(bounds.isEmpty());
2033 SkASSERT(fBounds.isEmpty());
2034 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002035 if (bounds.isEmpty()) {
2036 SkASSERT(fBounds.isEmpty());
2037 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002038 if (!fBounds.isEmpty()) {
2039 SkASSERT(fBounds.contains(bounds));
2040 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002041 }
reed@android.come522ca52009-11-23 20:10:41 +00002042 }
2043 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002044#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002045}
djsollen@google.com077348c2012-10-22 20:23:32 +00002046#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002047
reed@google.com04863fa2011-05-15 04:08:24 +00002048///////////////////////////////////////////////////////////////////////////////
2049
reed@google.com0b7b9822011-05-16 12:29:27 +00002050static int sign(SkScalar x) { return x < 0; }
2051#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002052
robertphillipsc506e302014-09-16 09:43:31 -07002053enum DirChange {
2054 kLeft_DirChange,
2055 kRight_DirChange,
2056 kStraight_DirChange,
2057 kBackwards_DirChange,
2058
2059 kInvalid_DirChange
2060};
2061
2062
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002063static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002064 // The error epsilon was empirically derived; worse case round rects
2065 // with a mid point outset by 2x float epsilon in tests had an error
2066 // of 12.
2067 const int epsilon = 16;
2068 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2069 return false;
2070 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002071 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002072 int aBits = SkFloatAs2sCompliment(compA);
2073 int bBits = SkFloatAs2sCompliment(compB);
2074 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002075}
2076
caryclarkb4216502015-03-02 13:02:34 -08002077static bool approximately_zero_when_compared_to(double x, double y) {
2078 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002079}
2080
caryclarkb4216502015-03-02 13:02:34 -08002081
reed@google.com04863fa2011-05-15 04:08:24 +00002082// only valid for a single contour
2083struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002084 Convexicator()
2085 : fPtCount(0)
2086 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002087 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002088 , fIsFinite(true)
2089 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002090 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002091 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002092 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002093 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002094 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002095 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002096
2097 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002098 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002099 }
2100
2101 SkPath::Convexity getConvexity() const { return fConvexity; }
2102
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002103 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002104 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002105
reed@google.com04863fa2011-05-15 04:08:24 +00002106 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002107 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002108 return;
2109 }
2110
2111 if (0 == fPtCount) {
2112 fCurrPt = pt;
2113 ++fPtCount;
2114 } else {
2115 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002116 SkScalar lengthSqd = vec.lengthSqd();
2117 if (!SkScalarIsFinite(lengthSqd)) {
2118 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002119 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002120 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002121 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002122 fCurrPt = pt;
2123 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002124 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002125 } else {
2126 SkASSERT(fPtCount > 2);
2127 this->addVec(vec);
2128 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002129
reed@google.com85b6e392011-05-15 20:25:17 +00002130 int sx = sign(vec.fX);
2131 int sy = sign(vec.fY);
2132 fDx += (sx != fSx);
2133 fDy += (sy != fSy);
2134 fSx = sx;
2135 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002136
reed@google.com85b6e392011-05-15 20:25:17 +00002137 if (fDx > 3 || fDy > 3) {
2138 fConvexity = SkPath::kConcave_Convexity;
2139 }
reed@google.com04863fa2011-05-15 04:08:24 +00002140 }
2141 }
2142 }
2143
2144 void close() {
2145 if (fPtCount > 2) {
2146 this->addVec(fFirstVec);
2147 }
2148 }
2149
caryclarkb4216502015-03-02 13:02:34 -08002150 DirChange directionChange(const SkVector& curVec) {
2151 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2152
2153 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2154 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2155 largest = SkTMax(largest, -smallest);
2156
2157 if (!almost_equal(largest, largest + cross)) {
2158 int sign = SkScalarSignAsInt(cross);
2159 if (sign) {
2160 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2161 }
2162 }
2163
2164 if (cross) {
2165 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2166 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2167 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2168 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2169 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2170 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2171 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2172 if (sign) {
2173 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2174 }
2175 }
2176 }
2177
2178 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2179 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2180 fLastVec.dot(curVec) < 0.0f) {
2181 return kBackwards_DirChange;
2182 }
2183
2184 return kStraight_DirChange;
2185 }
2186
2187
caryclarkd3d1a982014-12-08 04:57:38 -08002188 bool isFinite() const {
2189 return fIsFinite;
2190 }
2191
caryclark5ccef572015-03-02 10:07:56 -08002192 void setCurve(bool isCurve) {
2193 fIsCurve = isCurve;
2194 }
2195
reed@google.com04863fa2011-05-15 04:08:24 +00002196private:
2197 void addVec(const SkVector& vec) {
2198 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002199 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002200 switch (dir) {
2201 case kLeft_DirChange: // fall through
2202 case kRight_DirChange:
2203 if (kInvalid_DirChange == fExpectedDir) {
2204 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002205 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2206 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002207 } else if (dir != fExpectedDir) {
2208 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002209 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002210 }
2211 fLastVec = vec;
2212 break;
2213 case kStraight_DirChange:
2214 break;
2215 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002216 if (fIsCurve) {
2217 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002218 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002219 }
robertphillipsc506e302014-09-16 09:43:31 -07002220 fLastVec = vec;
2221 break;
2222 case kInvalid_DirChange:
2223 SkFAIL("Use of invalid direction change flag");
2224 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002225 }
2226 }
2227
caryclarkb4216502015-03-02 13:02:34 -08002228 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002229 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002230 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002231 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2232 // value with the current vec is deemed to be of a significant value.
2233 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002234 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002235 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002237 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002238 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002239 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002240 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002241};
2242
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002243SkPath::Convexity SkPath::internalGetConvexity() const {
2244 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002245 SkPoint pts[4];
2246 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002247 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002248
2249 int contourCount = 0;
2250 int count;
2251 Convexicator state;
2252
caryclarkd3d1a982014-12-08 04:57:38 -08002253 if (!isFinite()) {
2254 return kUnknown_Convexity;
2255 }
caryclarke8c56662015-07-14 11:19:26 -07002256 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002257 switch (verb) {
2258 case kMove_Verb:
2259 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002260 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002261 return kConcave_Convexity;
2262 }
2263 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002264 // fall through
2265 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002266 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002267 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002268 break;
caryclark5ccef572015-03-02 10:07:56 -08002269 case kQuad_Verb:
2270 // fall through
2271 case kConic_Verb:
2272 // fall through
2273 case kCubic_Verb:
2274 count = 2 + (kCubic_Verb == verb);
2275 // As an additional enhancement, this could set curve true only
2276 // if the curve is nonlinear
2277 state.setCurve(true);
2278 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002279 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002280 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002281 state.close();
2282 count = 0;
2283 break;
2284 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002285 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002286 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002287 return kConcave_Convexity;
2288 }
2289
2290 for (int i = 1; i <= count; i++) {
2291 state.addPt(pts[i]);
2292 }
2293 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002294 if (!state.isFinite()) {
2295 return kUnknown_Convexity;
2296 }
reed@google.com04863fa2011-05-15 04:08:24 +00002297 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002298 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 return kConcave_Convexity;
2300 }
2301 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002302 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002303 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2304 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002305 }
2306 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002307}
reed@google.com69a99432012-01-10 18:00:10 +00002308
2309///////////////////////////////////////////////////////////////////////////////
2310
2311class ContourIter {
2312public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002313 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002314
2315 bool done() const { return fDone; }
2316 // if !done() then these may be called
2317 int count() const { return fCurrPtCount; }
2318 const SkPoint* pts() const { return fCurrPt; }
2319 void next();
2320
2321private:
2322 int fCurrPtCount;
2323 const SkPoint* fCurrPt;
2324 const uint8_t* fCurrVerb;
2325 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002326 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002327 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002328 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002329};
2330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002331ContourIter::ContourIter(const SkPathRef& pathRef) {
2332 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002333 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002334 fCurrPt = pathRef.points();
2335 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002336 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002337 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002338 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002339 this->next();
2340}
2341
2342void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002343 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002344 fDone = true;
2345 }
2346 if (fDone) {
2347 return;
2348 }
2349
2350 // skip pts of prev contour
2351 fCurrPt += fCurrPtCount;
2352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002353 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002354 int ptCount = 1; // moveTo
2355 const uint8_t* verbs = fCurrVerb;
2356
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002357 for (--verbs; verbs > fStopVerbs; --verbs) {
2358 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002359 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002360 goto CONTOUR_END;
2361 case SkPath::kLine_Verb:
2362 ptCount += 1;
2363 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002364 case SkPath::kConic_Verb:
2365 fCurrConicWeight += 1;
2366 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002367 case SkPath::kQuad_Verb:
2368 ptCount += 2;
2369 break;
2370 case SkPath::kCubic_Verb:
2371 ptCount += 3;
2372 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002373 case SkPath::kClose_Verb:
2374 break;
2375 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002376 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002377 break;
2378 }
2379 }
2380CONTOUR_END:
2381 fCurrPtCount = ptCount;
2382 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002383 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002384}
2385
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002386// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002387static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002388 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2389 // We may get 0 when the above subtracts underflow. We expect this to be
2390 // very rare and lazily promote to double.
2391 if (0 == cross) {
2392 double p0x = SkScalarToDouble(p0.fX);
2393 double p0y = SkScalarToDouble(p0.fY);
2394
2395 double p1x = SkScalarToDouble(p1.fX);
2396 double p1y = SkScalarToDouble(p1.fY);
2397
2398 double p2x = SkScalarToDouble(p2.fX);
2399 double p2y = SkScalarToDouble(p2.fY);
2400
2401 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2402 (p1y - p0y) * (p2x - p0x));
2403
2404 }
2405 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002406}
2407
reed@google.comc1ea60a2012-01-31 15:15:36 +00002408// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002409static int find_max_y(const SkPoint pts[], int count) {
2410 SkASSERT(count > 0);
2411 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002412 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002413 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002414 SkScalar y = pts[i].fY;
2415 if (y > max) {
2416 max = y;
2417 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002418 }
2419 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002420 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002421}
2422
reed@google.comcabaf1d2012-01-11 21:03:05 +00002423static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2424 int i = index;
2425 for (;;) {
2426 i = (i + inc) % n;
2427 if (i == index) { // we wrapped around, so abort
2428 break;
2429 }
2430 if (pts[index] != pts[i]) { // found a different point, success!
2431 break;
2432 }
2433 }
2434 return i;
2435}
2436
reed@google.comc1ea60a2012-01-31 15:15:36 +00002437/**
2438 * Starting at index, and moving forward (incrementing), find the xmin and
2439 * xmax of the contiguous points that have the same Y.
2440 */
2441static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2442 int* maxIndexPtr) {
2443 const SkScalar y = pts[index].fY;
2444 SkScalar min = pts[index].fX;
2445 SkScalar max = min;
2446 int minIndex = index;
2447 int maxIndex = index;
2448 for (int i = index + 1; i < n; ++i) {
2449 if (pts[i].fY != y) {
2450 break;
2451 }
2452 SkScalar x = pts[i].fX;
2453 if (x < min) {
2454 min = x;
2455 minIndex = i;
2456 } else if (x > max) {
2457 max = x;
2458 maxIndex = i;
2459 }
2460 }
2461 *maxIndexPtr = maxIndex;
2462 return minIndex;
2463}
2464
reed026beb52015-06-10 14:23:15 -07002465static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2466 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002467}
2468
reed@google.comac8543f2012-01-30 20:51:25 +00002469/*
2470 * We loop through all contours, and keep the computed cross-product of the
2471 * contour that contained the global y-max. If we just look at the first
2472 * contour, we may find one that is wound the opposite way (correctly) since
2473 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2474 * that is outer most (or at least has the global y-max) before we can consider
2475 * its cross product.
2476 */
reed026beb52015-06-10 14:23:15 -07002477bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
2478 if (kUnknown_FirstDirection != path.fFirstDirection) {
2479 *dir = static_cast<FirstDirection>(path.fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002480 return true;
2481 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002482
2483 // don't want to pay the cost for computing this if it
2484 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002485 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2486 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
2487 *dir = static_cast<FirstDirection>(path.fFirstDirection);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002488 return false;
2489 }
reed@google.com69a99432012-01-10 18:00:10 +00002490
reed026beb52015-06-10 14:23:15 -07002491 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002492
reed@google.comac8543f2012-01-30 20:51:25 +00002493 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002494 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002495 SkScalar ymaxCross = 0;
2496
reed@google.com69a99432012-01-10 18:00:10 +00002497 for (; !iter.done(); iter.next()) {
2498 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002499 if (n < 3) {
2500 continue;
2501 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002502
reed@google.comcabaf1d2012-01-11 21:03:05 +00002503 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002504 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002505 int index = find_max_y(pts, n);
2506 if (pts[index].fY < ymax) {
2507 continue;
2508 }
2509
2510 // If there is more than 1 distinct point at the y-max, we take the
2511 // x-min and x-max of them and just subtract to compute the dir.
2512 if (pts[(index + 1) % n].fY == pts[index].fY) {
2513 int maxIndex;
2514 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2515 if (minIndex == maxIndex) {
2516 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002517 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002518 SkASSERT(pts[minIndex].fY == pts[index].fY);
2519 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2520 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2521 // we just subtract the indices, and let that auto-convert to
2522 // SkScalar, since we just want - or + to signal the direction.
2523 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002524 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002525 TRY_CROSSPROD:
2526 // Find a next and prev index to use for the cross-product test,
2527 // but we try to find pts that form non-zero vectors from pts[index]
2528 //
2529 // Its possible that we can't find two non-degenerate vectors, so
2530 // we have to guard our search (e.g. all the pts could be in the
2531 // same place).
2532
2533 // we pass n - 1 instead of -1 so we don't foul up % operator by
2534 // passing it a negative LH argument.
2535 int prev = find_diff_pt(pts, index, n, n - 1);
2536 if (prev == index) {
2537 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002538 continue;
2539 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002540 int next = find_diff_pt(pts, index, n, 1);
2541 SkASSERT(next != index);
2542 cross = cross_prod(pts[prev], pts[index], pts[next]);
2543 // if we get a zero and the points are horizontal, then we look at the spread in
2544 // x-direction. We really should continue to walk away from the degeneracy until
2545 // there is a divergence.
2546 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2547 // construct the subtract so we get the correct Direction below
2548 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002549 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002550 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002551
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002552 if (cross) {
2553 // record our best guess so far
2554 ymax = pts[index].fY;
2555 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002556 }
2557 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002558 if (ymaxCross) {
2559 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002560 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002561 return true;
2562 } else {
2563 return false;
2564 }
reed@google.comac8543f2012-01-30 20:51:25 +00002565}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002566
2567///////////////////////////////////////////////////////////////////////////////
2568
2569static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2570 SkScalar D, SkScalar t) {
2571 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2572}
2573
2574static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2575 SkScalar t) {
2576 SkScalar A = c3 + 3*(c1 - c2) - c0;
2577 SkScalar B = 3*(c2 - c1 - c1 + c0);
2578 SkScalar C = 3*(c1 - c0);
2579 SkScalar D = c0;
2580 return eval_cubic_coeff(A, B, C, D, t);
2581}
2582
2583/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2584 t value such that cubic(t) = target
2585 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002586static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002587 SkScalar target, SkScalar* t) {
2588 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2589 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002590
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002591 SkScalar D = c0 - target;
2592 SkScalar A = c3 + 3*(c1 - c2) - c0;
2593 SkScalar B = 3*(c2 - c1 - c1 + c0);
2594 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002595
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002596 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2597 SkScalar minT = 0;
2598 SkScalar maxT = SK_Scalar1;
2599 SkScalar mid;
2600 int i;
2601 for (i = 0; i < 16; i++) {
2602 mid = SkScalarAve(minT, maxT);
2603 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2604 if (delta < 0) {
2605 minT = mid;
2606 delta = -delta;
2607 } else {
2608 maxT = mid;
2609 }
2610 if (delta < TOLERANCE) {
2611 break;
2612 }
2613 }
2614 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002615}
2616
2617template <size_t N> static void find_minmax(const SkPoint pts[],
2618 SkScalar* minPtr, SkScalar* maxPtr) {
2619 SkScalar min, max;
2620 min = max = pts[0].fX;
2621 for (size_t i = 1; i < N; ++i) {
2622 min = SkMinScalar(min, pts[i].fX);
2623 max = SkMaxScalar(max, pts[i].fX);
2624 }
2625 *minPtr = min;
2626 *maxPtr = max;
2627}
2628
2629static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2630 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002631
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002632 int dir = 1;
2633 if (pts[0].fY > pts[3].fY) {
2634 storage[0] = pts[3];
2635 storage[1] = pts[2];
2636 storage[2] = pts[1];
2637 storage[3] = pts[0];
2638 pts = storage;
2639 dir = -1;
2640 }
2641 if (y < pts[0].fY || y >= pts[3].fY) {
2642 return 0;
2643 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002644
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002645 // quickreject or quickaccept
2646 SkScalar min, max;
2647 find_minmax<4>(pts, &min, &max);
2648 if (x < min) {
2649 return 0;
2650 }
2651 if (x > max) {
2652 return dir;
2653 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002654
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002655 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002656 SkScalar t;
2657 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2658 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002659 return xt < x ? dir : 0;
2660}
2661
2662static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2663 SkPoint dst[10];
2664 int n = SkChopCubicAtYExtrema(pts, dst);
2665 int w = 0;
2666 for (int i = 0; i <= n; ++i) {
2667 w += winding_mono_cubic(&dst[i * 3], x, y);
2668 }
2669 return w;
2670}
2671
2672static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2673 SkScalar y0 = pts[0].fY;
2674 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002675
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002676 int dir = 1;
2677 if (y0 > y2) {
2678 SkTSwap(y0, y2);
2679 dir = -1;
2680 }
2681 if (y < y0 || y >= y2) {
2682 return 0;
2683 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002684
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002685 // bounds check on X (not required. is it faster?)
2686#if 0
2687 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2688 return 0;
2689 }
2690#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002691
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002692 SkScalar roots[2];
2693 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2694 2 * (pts[1].fY - pts[0].fY),
2695 pts[0].fY - y,
2696 roots);
2697 SkASSERT(n <= 1);
2698 SkScalar xt;
2699 if (0 == n) {
2700 SkScalar mid = SkScalarAve(y0, y2);
2701 // Need [0] and [2] if dir == 1
2702 // and [2] and [0] if dir == -1
2703 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2704 } else {
2705 SkScalar t = roots[0];
2706 SkScalar C = pts[0].fX;
2707 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2708 SkScalar B = 2 * (pts[1].fX - C);
2709 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2710 }
2711 return xt < x ? dir : 0;
2712}
2713
2714static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2715 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2716 if (y0 == y1) {
2717 return true;
2718 }
2719 if (y0 < y1) {
2720 return y1 <= y2;
2721 } else {
2722 return y1 >= y2;
2723 }
2724}
2725
2726static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2727 SkPoint dst[5];
2728 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002729
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2731 n = SkChopQuadAtYExtrema(pts, dst);
2732 pts = dst;
2733 }
2734 int w = winding_mono_quad(pts, x, y);
2735 if (n > 0) {
2736 w += winding_mono_quad(&pts[2], x, y);
2737 }
2738 return w;
2739}
2740
2741static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2742 SkScalar x0 = pts[0].fX;
2743 SkScalar y0 = pts[0].fY;
2744 SkScalar x1 = pts[1].fX;
2745 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002746
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749 int dir = 1;
2750 if (y0 > y1) {
2751 SkTSwap(y0, y1);
2752 dir = -1;
2753 }
2754 if (y < y0 || y >= y1) {
2755 return 0;
2756 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002757
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2759 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002760
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002761 if (SkScalarSignAsInt(cross) == dir) {
2762 dir = 0;
2763 }
2764 return dir;
2765}
2766
reed@google.com4db592c2013-10-30 17:39:43 +00002767static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2768 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2769}
2770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771bool SkPath::contains(SkScalar x, SkScalar y) const {
2772 bool isInverse = this->isInverseFillType();
2773 if (this->isEmpty()) {
2774 return isInverse;
2775 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002776
reed@google.com4db592c2013-10-30 17:39:43 +00002777 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 return isInverse;
2779 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002780
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 SkPath::Iter iter(*this, true);
2782 bool done = false;
2783 int w = 0;
2784 do {
2785 SkPoint pts[4];
2786 switch (iter.next(pts, false)) {
2787 case SkPath::kMove_Verb:
2788 case SkPath::kClose_Verb:
2789 break;
2790 case SkPath::kLine_Verb:
2791 w += winding_line(pts, x, y);
2792 break;
2793 case SkPath::kQuad_Verb:
2794 w += winding_quad(pts, x, y);
2795 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002796 case SkPath::kConic_Verb:
2797 SkASSERT(0);
2798 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 case SkPath::kCubic_Verb:
2800 w += winding_cubic(pts, x, y);
2801 break;
2802 case SkPath::kDone_Verb:
2803 done = true;
2804 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002805 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002807
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 switch (this->getFillType()) {
2809 case SkPath::kEvenOdd_FillType:
2810 case SkPath::kInverseEvenOdd_FillType:
2811 w &= 1;
2812 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002813 default:
2814 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002815 }
2816 return SkToBool(w);
2817}