blob: 351629d1252ca2bde200dae76ab48e58c8718033 [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) {
herb9f4dbca2015-09-28 11:05:47 -070039 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
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;
herb9f4dbca2015-09-28 11:05:47 -0700169 // Simulate fFirstDirection = that.fFirstDirection;
170 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700171 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172}
173
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000174bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000175 // note: don't need to look at isConvex or bounds, since just comparing the
176 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000177 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000178 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700183 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000184 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700187 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
188 uint8_t temp = fFirstDirection;
189 fFirstDirection.store(that.fFirstDirection.load());
190 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700191 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 }
193}
194
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000195static inline bool check_edge_against_rect(const SkPoint& p0,
196 const SkPoint& p1,
197 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700198 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000199 const SkPoint* edgeBegin;
200 SkVector v;
reed026beb52015-06-10 14:23:15 -0700201 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000202 v = p1 - p0;
203 edgeBegin = &p0;
204 } else {
205 v = p0 - p1;
206 edgeBegin = &p1;
207 }
208 if (v.fX || v.fY) {
209 // check the cross product of v with the vec from edgeBegin to each rect corner
210 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
211 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
212 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
213 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
214 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
215 return false;
216 }
217 }
218 return true;
219}
220
221bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
222 // This only handles non-degenerate convex paths currently.
223 if (kConvex_Convexity != this->getConvexity()) {
224 return false;
225 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000226
reed026beb52015-06-10 14:23:15 -0700227 SkPathPriv::FirstDirection direction;
228 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000229 return false;
230 }
231
232 SkPoint firstPt;
233 SkPoint prevPt;
234 RawIter iter(*this);
235 SkPath::Verb verb;
236 SkPoint pts[4];
237 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000238 SkDEBUGCODE(int segmentCount = 0;)
239 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240
241 while ((verb = iter.next(pts)) != kDone_Verb) {
242 int nextPt = -1;
243 switch (verb) {
244 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000245 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246 SkDEBUGCODE(++moveCnt);
247 firstPt = prevPt = pts[0];
248 break;
249 case kLine_Verb:
250 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000251 SkASSERT(moveCnt && !closeCount);
252 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 break;
254 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000255 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000256 SkASSERT(moveCnt && !closeCount);
257 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 nextPt = 2;
259 break;
260 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 nextPt = 3;
264 break;
265 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000267 break;
268 default:
269 SkDEBUGFAIL("unknown verb");
270 }
271 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800272 if (SkPath::kConic_Verb == verb) {
273 SkConic orig;
274 orig.set(pts, iter.conicWeight());
275 SkPoint quadPts[5];
276 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
277 SK_ALWAYSBREAK(2 == count);
278
279 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
280 return false;
281 }
282 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
283 return false;
284 }
285 } else {
286 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
287 return false;
288 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 }
290 prevPt = pts[nextPt];
291 }
292 }
293
294 return check_edge_against_rect(prevPt, firstPt, rect, direction);
295}
296
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000297uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000298 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800299#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000300 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
301 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
302#endif
303 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000304}
djsollen@google.come63793a2012-03-21 15:39:03 +0000305
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306void SkPath::reset() {
307 SkDEBUGCODE(this->validate();)
308
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000309 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000310 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311}
312
313void SkPath::rewind() {
314 SkDEBUGCODE(this->validate();)
315
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000316 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000317 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318}
319
reed@google.com7e6c4d12012-05-10 14:05:43 +0000320bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000322
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000323 if (2 == verbCount) {
324 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
325 if (kLine_Verb == fPathRef->atVerb(1)) {
326 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000327 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000328 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000329 line[0] = pts[0];
330 line[1] = pts[1];
331 }
332 return true;
333 }
334 }
335 return false;
336}
337
caryclark@google.comf1316942011-07-26 19:54:45 +0000338/*
339 Determines if path is a rect by keeping track of changes in direction
340 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000341
caryclark@google.comf1316942011-07-26 19:54:45 +0000342 The direction is computed such that:
343 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000344 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000345 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000346 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000347
caryclark@google.comf1316942011-07-26 19:54:45 +0000348A rectangle cycles up/right/down/left or up/left/down/right.
349
350The test fails if:
351 The path is closed, and followed by a line.
352 A second move creates a new endpoint.
353 A diagonal line is parsed.
354 There's more than four changes of direction.
355 There's a discontinuity on the line (e.g., a move in the middle)
356 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000357 The path contains a quadratic or cubic.
358 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000359 *The rectangle doesn't complete a cycle.
360 *The final point isn't equal to the first point.
361
362 *These last two conditions we relax if we have a 3-edge path that would
363 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000364
caryclark@google.comf1316942011-07-26 19:54:45 +0000365It's OK if the path has:
366 Several colinear line segments composing a rectangle side.
367 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000368
caryclark@google.comf1316942011-07-26 19:54:45 +0000369The direction takes advantage of the corners found since opposite sides
370must travel in opposite directions.
371
372FIXME: Allow colinear quads and cubics to be treated like lines.
373FIXME: If the API passes fill-only, return true if the filled stroke
374 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000375
376 first,last,next direction state-machine:
377 0x1 is set if the segment is horizontal
378 0x2 is set if the segment is moving to the right or down
379 thus:
380 two directions are opposites iff (dirA ^ dirB) == 0x2
381 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000382
caryclark@google.comf1316942011-07-26 19:54:45 +0000383 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000384static int rect_make_dir(SkScalar dx, SkScalar dy) {
385 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
386}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000387bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
388 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000389 int corners = 0;
390 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000391 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700392 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000393 first.set(0, 0);
394 last.set(0, 0);
395 int firstDirection = 0;
396 int lastDirection = 0;
397 int nextDirection = 0;
398 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000399 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700400 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000401 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000402 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700403 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
404 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 savePts = pts;
407 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700409 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000410 case kLine_Verb: {
411 SkScalar left = last.fX;
412 SkScalar top = last.fY;
413 SkScalar right = pts->fX;
414 SkScalar bottom = pts->fY;
415 ++pts;
416 if (left != right && top != bottom) {
417 return false; // diagonal
418 }
419 if (left == right && top == bottom) {
420 break; // single point on side OK
421 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000422 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000423 if (0 == corners) {
424 firstDirection = nextDirection;
425 first = last;
426 last = pts[-1];
427 corners = 1;
428 closedOrMoved = false;
429 break;
430 }
431 if (closedOrMoved) {
432 return false; // closed followed by a line
433 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000434 if (autoClose && nextDirection == firstDirection) {
435 break; // colinear with first
436 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000437 closedOrMoved = autoClose;
438 if (lastDirection != nextDirection) {
439 if (++corners > 4) {
440 return false; // too many direction changes
441 }
442 }
443 last = pts[-1];
444 if (lastDirection == nextDirection) {
445 break; // colinear segment
446 }
447 // Possible values for corners are 2, 3, and 4.
448 // When corners == 3, nextDirection opposes firstDirection.
449 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000450 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
452 if ((directionCycle ^ turn) != nextDirection) {
453 return false; // direction didn't follow cycle
454 }
455 break;
456 }
457 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000458 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 case kCubic_Verb:
460 return false; // quadratic, cubic not allowed
461 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700462 if (allowPartial && !autoClose && firstDirection) {
463 insertClose = true;
464 *currVerb -= 1; // try move again afterwards
465 goto addMissingClose;
466 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000467 last = *pts++;
468 closedOrMoved = true;
469 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000470 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000471 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000472 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000474 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700476addMissingClose:
477 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 }
479 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000480 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000481 if (!result) {
482 // check if we are just an incomplete rectangle, in which case we can
483 // return true, but not claim to be closed.
484 // e.g.
485 // 3 sided rectangle
486 // 4 sided but the last edge is not long enough to reach the start
487 //
488 SkScalar closeX = first.x() - last.x();
489 SkScalar closeY = first.y() - last.y();
490 if (closeX && closeY) {
491 return false; // we're diagonal, abort (can we ever reach this?)
492 }
493 int closeDirection = rect_make_dir(closeX, closeY);
494 // make sure the close-segment doesn't double-back on itself
495 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
496 result = true;
497 autoClose = false; // we are not closed
498 }
499 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000500 if (savePts) {
501 *ptsPtr = savePts;
502 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 if (result && isClosed) {
504 *isClosed = autoClose;
505 }
506 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000507 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000508 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000509 return result;
510}
511
robertphillips4f662e62014-12-29 14:06:51 -0800512bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000513 SkDEBUGCODE(this->validate();)
514 int currVerb = 0;
515 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800516 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800517 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800518 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800520 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800521 int32_t num = SkToS32(pts - first);
522 if (num) {
523 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800524 } else {
525 // 'pts' isn't updated for open rects
526 *rect = this->getBounds();
527 }
528 }
529 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000530}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000531
caryclark95bc5f32015-04-08 08:34:15 -0700532bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 SkDEBUGCODE(this->validate();)
534 int currVerb = 0;
535 const SkPoint* pts = fPathRef->points();
536 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000537 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700538 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 return false;
540 }
541 const SkPoint* last = pts;
542 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700543 bool isClosed;
544 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700546 if (!isClosed) {
547 pts = fPathRef->points() + fPathRef->countPoints();
548 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000549 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 if (testRects[0].contains(testRects[1])) {
551 if (rects) {
552 rects[0] = testRects[0];
553 rects[1] = testRects[1];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[0];
557 dirs[1] = testDirs[1];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 if (testRects[1].contains(testRects[0])) {
562 if (rects) {
563 rects[0] = testRects[1];
564 rects[1] = testRects[0];
565 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000566 if (dirs) {
567 dirs[0] = testDirs[1];
568 dirs[1] = testDirs[0];
569 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 return true;
571 }
572 }
573 return false;
574}
575
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576int SkPath::countPoints() const {
577 return fPathRef->countPoints();
578}
579
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000580int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 SkDEBUGCODE(this->validate();)
582
583 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000584 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585 int count = SkMin32(max, fPathRef->countPoints());
586 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
587 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588}
589
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000591 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
592 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000593 }
594 return SkPoint::Make(0, 0);
595}
596
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000597int SkPath::countVerbs() const {
598 return fPathRef->countVerbs();
599}
600
601static inline void copy_verbs_reverse(uint8_t* inorderDst,
602 const uint8_t* reversedSrc,
603 int count) {
604 for (int i = 0; i < count; ++i) {
605 inorderDst[i] = reversedSrc[~i];
606 }
607}
608
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000609int SkPath::getVerbs(uint8_t dst[], int max) const {
610 SkDEBUGCODE(this->validate();)
611
612 SkASSERT(max >= 0);
613 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 int count = SkMin32(max, fPathRef->countVerbs());
615 copy_verbs_reverse(dst, fPathRef->verbs(), count);
616 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617}
618
reed@google.com294dd7b2011-10-11 11:58:32 +0000619bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 SkDEBUGCODE(this->validate();)
621
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000623 if (count > 0) {
624 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000627 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000629 if (lastPt) {
630 lastPt->set(0, 0);
631 }
632 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633}
634
caryclarkaec25102015-04-29 08:28:30 -0700635void SkPath::setPt(int index, SkScalar x, SkScalar y) {
636 SkDEBUGCODE(this->validate();)
637
638 int count = fPathRef->countPoints();
639 if (count <= index) {
640 return;
641 } else {
642 SkPathRef::Editor ed(&fPathRef);
643 ed.atPoint(index)->set(x, y);
644 }
645}
646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647void SkPath::setLastPt(SkScalar x, SkScalar y) {
648 SkDEBUGCODE(this->validate();)
649
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651 if (count == 0) {
652 this->moveTo(x, y);
653 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 SkPathRef::Editor ed(&fPathRef);
655 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 }
657}
658
reed@google.com04863fa2011-05-15 04:08:24 +0000659void SkPath::setConvexity(Convexity c) {
660 if (fConvexity != c) {
661 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000662 }
663}
664
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665//////////////////////////////////////////////////////////////////////////////
666// Construction methods
667
reed026beb52015-06-10 14:23:15 -0700668#define DIRTY_AFTER_EDIT \
669 do { \
670 fConvexity = kUnknown_Convexity; \
671 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000672 } while (0)
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674void SkPath::incReserve(U16CPU inc) {
675 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 SkDEBUGCODE(this->validate();)
678}
679
680void SkPath::moveTo(SkScalar x, SkScalar y) {
681 SkDEBUGCODE(this->validate();)
682
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000685 // remember our index
686 fLastMoveToIndex = fPathRef->countPoints();
687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700689
690 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691}
692
693void SkPath::rMoveTo(SkScalar x, SkScalar y) {
694 SkPoint pt;
695 this->getLastPt(&pt);
696 this->moveTo(pt.fX + x, pt.fY + y);
697}
698
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000699void SkPath::injectMoveToIfNeeded() {
700 if (fLastMoveToIndex < 0) {
701 SkScalar x, y;
702 if (fPathRef->countVerbs() == 0) {
703 x = y = 0;
704 } else {
705 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
706 x = pt.fX;
707 y = pt.fY;
708 }
709 this->moveTo(x, y);
710 }
711}
712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713void SkPath::lineTo(SkScalar x, SkScalar y) {
714 SkDEBUGCODE(this->validate();)
715
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000716 this->injectMoveToIfNeeded();
717
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000718 SkPathRef::Editor ed(&fPathRef);
719 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720
reed@google.comb54455e2011-05-16 14:16:04 +0000721 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722}
723
724void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000725 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 SkPoint pt;
727 this->getLastPt(&pt);
728 this->lineTo(pt.fX + x, pt.fY + y);
729}
730
731void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
732 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000733
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000734 this->injectMoveToIfNeeded();
735
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000736 SkPathRef::Editor ed(&fPathRef);
737 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738 pts[0].set(x1, y1);
739 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000740
reed@google.comb54455e2011-05-16 14:16:04 +0000741 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742}
743
744void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000745 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 SkPoint pt;
747 this->getLastPt(&pt);
748 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
749}
750
reed@google.com277c3f82013-05-31 15:17:50 +0000751void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
752 SkScalar w) {
753 // check for <= 0 or NaN with this test
754 if (!(w > 0)) {
755 this->lineTo(x2, y2);
756 } else if (!SkScalarIsFinite(w)) {
757 this->lineTo(x1, y1);
758 this->lineTo(x2, y2);
759 } else if (SK_Scalar1 == w) {
760 this->quadTo(x1, y1, x2, y2);
761 } else {
762 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000763
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000764 this->injectMoveToIfNeeded();
765
reed@google.com277c3f82013-05-31 15:17:50 +0000766 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000767 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000768 pts[0].set(x1, y1);
769 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000770
reed@google.com277c3f82013-05-31 15:17:50 +0000771 DIRTY_AFTER_EDIT;
772 }
773}
774
775void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
776 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000777 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000778 SkPoint pt;
779 this->getLastPt(&pt);
780 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
781}
782
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
784 SkScalar x3, SkScalar y3) {
785 SkDEBUGCODE(this->validate();)
786
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000787 this->injectMoveToIfNeeded();
788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000789 SkPathRef::Editor ed(&fPathRef);
790 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 pts[0].set(x1, y1);
792 pts[1].set(x2, y2);
793 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794
reed@google.comb54455e2011-05-16 14:16:04 +0000795 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796}
797
798void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
799 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000800 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 SkPoint pt;
802 this->getLastPt(&pt);
803 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
804 pt.fX + x3, pt.fY + y3);
805}
806
807void SkPath::close() {
808 SkDEBUGCODE(this->validate();)
809
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000810 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000812 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 case kLine_Verb:
814 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000815 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 case kMove_Verb: {
818 SkPathRef::Editor ed(&fPathRef);
819 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 }
reed@google.com277c3f82013-05-31 15:17:50 +0000822 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000823 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000824 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000825 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000826 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000827 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 }
829 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000830
831 // signal that we need a moveTo to follow us (unless we're done)
832#if 0
833 if (fLastMoveToIndex >= 0) {
834 fLastMoveToIndex = ~fLastMoveToIndex;
835 }
836#else
837 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
838#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839}
840
841///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000842
fmalitac08d53e2015-11-17 09:53:29 -0800843namespace {
844
845template <unsigned N>
846class PointIterator {
847public:
848 PointIterator(SkPath::Direction dir, unsigned startIndex)
849 : fCurrent(startIndex % N)
850 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
851
852 const SkPoint& current() const {
853 SkASSERT(fCurrent < N);
854 return fPts[fCurrent];
855 }
856
857 const SkPoint& next() {
858 fCurrent = (fCurrent + fAdvance) % N;
859 return this->current();
860 }
861
862protected:
863 SkPoint fPts[N];
864
865private:
866 unsigned fCurrent;
867 unsigned fAdvance;
868};
869
870class RectPointIterator : public PointIterator<4> {
871public:
872 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
873 : PointIterator(dir, startIndex) {
874
875 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
876 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
877 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
878 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
879 }
880};
881
882class OvalPointIterator : public PointIterator<4> {
883public:
884 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
885 : PointIterator(dir, startIndex) {
886
887 const SkScalar cx = oval.centerX();
888 const SkScalar cy = oval.centerY();
889
890 fPts[0] = SkPoint::Make(cx, oval.fTop);
891 fPts[1] = SkPoint::Make(oval.fRight, cy);
892 fPts[2] = SkPoint::Make(cx, oval.fBottom);
893 fPts[3] = SkPoint::Make(oval.fLeft, cy);
894 }
895};
896
897class RRectPointIterator : public PointIterator<8> {
898public:
899 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
900 : PointIterator(dir, startIndex) {
901
902 const SkRect& bounds = rrect.getBounds();
903 const SkScalar L = bounds.fLeft;
904 const SkScalar T = bounds.fTop;
905 const SkScalar R = bounds.fRight;
906 const SkScalar B = bounds.fBottom;
907
908 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
909 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
910 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
911 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
912 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
913 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
914 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
915 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
916 }
917};
918
919} // anonymous namespace
920
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000921static void assert_known_direction(int dir) {
922 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
923}
924
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800926 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927}
928
929void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
930 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800931 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
932}
933
934void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000935 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700936 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800937 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000938 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800939 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000940
fmalitac08d53e2015-11-17 09:53:29 -0800941 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942
fmalitac08d53e2015-11-17 09:53:29 -0800943 const int kVerbs = 5; // moveTo + 3x lineTo + close
944 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945
fmalitac08d53e2015-11-17 09:53:29 -0800946 RectPointIterator iter(rect, dir, startIndex);
947
948 this->moveTo(iter.current());
949 this->lineTo(iter.next());
950 this->lineTo(iter.next());
951 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800953
954 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955}
956
reed@google.com744faba2012-05-29 19:54:52 +0000957void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
958 SkDEBUGCODE(this->validate();)
959 if (count <= 0) {
960 return;
961 }
962
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000963 fLastMoveToIndex = fPathRef->countPoints();
964
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000965 // +close makes room for the extra kClose_Verb
966 SkPathRef::Editor ed(&fPathRef, count+close, count);
967
968 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000969 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000970 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
971 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000972 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000973
reed@google.com744faba2012-05-29 19:54:52 +0000974 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000975 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800976 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000977 }
978
reed@google.com744faba2012-05-29 19:54:52 +0000979 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000980 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000981}
982
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000983#include "SkGeometry.h"
984
reedf90ea012015-01-29 12:03:58 -0800985static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
986 SkPoint* pt) {
987 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000988 // Chrome uses this path to move into and out of ovals. If not
989 // treated as a special case the moves can distort the oval's
990 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800991 pt->set(oval.fRight, oval.centerY());
992 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000993 } else if (0 == oval.width() && 0 == oval.height()) {
994 // Chrome will sometimes create 0 radius round rects. Having degenerate
995 // quad segments in the path prevents the path from being recognized as
996 // a rect.
997 // TODO: optimizing the case where only one of width or height is zero
998 // should also be considered. This case, however, doesn't seem to be
999 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001000 pt->set(oval.fRight, oval.fTop);
1001 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001002 }
reedf90ea012015-01-29 12:03:58 -08001003 return false;
1004}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001005
reedd5d27d92015-02-09 13:54:43 -08001006// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1007//
1008static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1009 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1010 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1011 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001012
1013 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001014 loss in radians-conversion and/or sin/cos, we may end up with coincident
1015 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1016 of drawing a nearly complete circle (good).
1017 e.g. canvas.drawArc(0, 359.99, ...)
1018 -vs- canvas.drawArc(0, 359.9, ...)
1019 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001020 */
reedd5d27d92015-02-09 13:54:43 -08001021 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001022 SkScalar sw = SkScalarAbs(sweepAngle);
1023 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1024 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1025 // make a guess at a tiny angle (in radians) to tweak by
1026 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1027 // not sure how much will be enough, so we use a loop
1028 do {
1029 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001030 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1031 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001032 }
1033 }
reedd5d27d92015-02-09 13:54:43 -08001034 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1035}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001036
reed9e779d42015-02-17 11:43:14 -08001037/**
1038 * If this returns 0, then the caller should just line-to the singlePt, else it should
1039 * ignore singlePt and append the specified number of conics.
1040 */
reedd5d27d92015-02-09 13:54:43 -08001041static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001042 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1043 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001044 SkMatrix matrix;
1045
1046 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1047 matrix.postTranslate(oval.centerX(), oval.centerY());
1048
reed9e779d42015-02-17 11:43:14 -08001049 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1050 if (0 == count) {
1051 matrix.mapXY(start.x(), start.y(), singlePt);
1052 }
1053 return count;
reedd5d27d92015-02-09 13:54:43 -08001054}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001056void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001058 SkRRect rrect;
1059 rrect.setRectRadii(rect, (const SkVector*) radii);
1060 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061}
1062
reed@google.com4ed0fb72012-12-12 20:48:18 +00001063void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001064 // legacy start indices: 6 (CW) and 7(CCW)
1065 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1066}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001067
fmalitac08d53e2015-11-17 09:53:29 -08001068void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1069 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001070
fmalitac08d53e2015-11-17 09:53:29 -08001071 if (rrect.isEmpty()) {
1072 return;
reed1b28a3a2014-12-17 14:42:09 -08001073 }
fmalitac08d53e2015-11-17 09:53:29 -08001074
caryclarkda707bf2015-11-19 14:47:43 -08001075 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001076 const SkRect& bounds = rrect.getBounds();
1077
1078 if (rrect.isRect()) {
1079 // degenerate(rect) => radii points are collapsing
1080 this->addRect(bounds, dir, (startIndex + 1) / 2);
1081 } else if (rrect.isOval()) {
1082 // degenerate(oval) => line points are collapsing
1083 this->addOval(bounds, dir, startIndex / 2);
1084 } else {
1085 fFirstDirection = this->hasOnlyMoveTos() ?
1086 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1087
1088 SkAutoPathBoundsUpdate apbu(this, bounds);
1089 SkAutoDisableDirectionCheck addc(this);
1090
1091 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1092 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1093 const SkScalar weight = SK_ScalarRoot2Over2;
1094
1095 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1096 const int kVerbs = startsWithConic
1097 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1098 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1099 this->incReserve(kVerbs);
1100
1101 RRectPointIterator rrectIter(rrect, dir, startIndex);
1102 // Corner iterator indices follow the collapsed radii model,
1103 // adjusted such that the start pt is "behind" the radii start pt.
1104 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1105 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1106
1107 this->moveTo(rrectIter.current());
1108 if (startsWithConic) {
1109 for (unsigned i = 0; i < 3; ++i) {
1110 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1111 this->lineTo(rrectIter.next());
1112 }
1113 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1114 // final lineTo handled by close().
1115 } else {
1116 for (unsigned i = 0; i < 4; ++i) {
1117 this->lineTo(rrectIter.next());
1118 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1119 }
1120 }
1121 this->close();
1122
caryclarkda707bf2015-11-19 14:47:43 -08001123 SkPathRef::Editor ed(&fPathRef);
1124 ed.setIsRRect(isRRect);
1125
fmalitac08d53e2015-11-17 09:53:29 -08001126 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1127 }
1128
1129 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001130}
1131
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001132bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001133 int count = fPathRef->countVerbs();
1134 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1135 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001136 if (*verbs == kLine_Verb ||
1137 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001138 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001139 *verbs == kCubic_Verb) {
1140 return false;
1141 }
1142 ++verbs;
1143 }
1144 return true;
1145}
1146
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1148 Direction dir) {
1149 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001150
humper@google.com75e3ca12013-04-08 21:44:11 +00001151 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001152 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001153 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001154 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001155 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1156 return;
1157 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001158
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001159 SkRRect rrect;
1160 rrect.setRectXY(rect, rx, ry);
1161 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001162}
1163
reed@android.com8a1c16f2008-12-17 15:59:43 +00001164void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001165 // legacy start index: 1
1166 this->addOval(oval, dir, 1);
1167}
1168
1169void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001170 assert_known_direction(dir);
1171
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172 /* If addOval() is called after previous moveTo(),
1173 this path is still marked as an oval. This is used to
1174 fit into WebKit's calling sequences.
1175 We can't simply check isEmpty() in this case, as additional
1176 moveTo() would mark the path non empty.
1177 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001178 bool isOval = hasOnlyMoveTos();
1179 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001180 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001181 } else {
reed026beb52015-06-10 14:23:15 -07001182 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001183 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001184
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001185 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186 SkAutoPathBoundsUpdate apbu(this, oval);
1187
fmalitac08d53e2015-11-17 09:53:29 -08001188 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1189 const int kVerbs = 6; // moveTo + 4x conicTo + close
1190 this->incReserve(kVerbs);
1191
1192 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1193 // The corner iterator pts are tracking "behind" the oval/radii pts.
1194 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001195 const SkScalar weight = SK_ScalarRoot2Over2;
1196
fmalitac08d53e2015-11-17 09:53:29 -08001197 this->moveTo(ovalIter.current());
1198 for (unsigned i = 0; i < 4; ++i) {
1199 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001200 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202
fmalitac08d53e2015-11-17 09:53:29 -08001203 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1204
robertphillips@google.com466310d2013-12-03 16:43:54 +00001205 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001206
robertphillips@google.com466310d2013-12-03 16:43:54 +00001207 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001208}
1209
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1211 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001212 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 }
1214}
1215
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1217 bool forceMoveTo) {
1218 if (oval.width() < 0 || oval.height() < 0) {
1219 return;
1220 }
1221
reedf90ea012015-01-29 12:03:58 -08001222 if (fPathRef->countVerbs() == 0) {
1223 forceMoveTo = true;
1224 }
1225
1226 SkPoint lonePt;
1227 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1228 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1229 return;
1230 }
1231
reedd5d27d92015-02-09 13:54:43 -08001232 SkVector startV, stopV;
1233 SkRotationDirection dir;
1234 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1235
reed9e779d42015-02-17 11:43:14 -08001236 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001237 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001238 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001239 if (count) {
1240 this->incReserve(count * 2 + 1);
1241 const SkPoint& pt = conics[0].fPts[0];
1242 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1243 for (int i = 0; i < count; ++i) {
1244 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1245 }
reed9e779d42015-02-17 11:43:14 -08001246 } else {
1247 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001248 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249}
1250
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001251void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 if (oval.isEmpty() || 0 == sweepAngle) {
1253 return;
1254 }
1255
1256 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1257
1258 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1259 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001260 } else {
1261 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263}
1264
1265/*
1266 Need to handle the case when the angle is sharp, and our computed end-points
1267 for the arc go behind pt1 and/or p2...
1268*/
reedc7789042015-01-29 12:59:11 -08001269void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001270 if (radius == 0) {
1271 this->lineTo(x1, y1);
1272 return;
1273 }
1274
1275 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 // need to know our prev pt so we can construct tangent vectors
1278 {
1279 SkPoint start;
1280 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001281 // Handle degenerate cases by adding a line to the first point and
1282 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 before.setNormalize(x1 - start.fX, y1 - start.fY);
1284 after.setNormalize(x2 - x1, y2 - y1);
1285 }
reed@google.comabf15c12011-01-18 20:35:51 +00001286
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 SkScalar cosh = SkPoint::DotProduct(before, after);
1288 SkScalar sinh = SkPoint::CrossProduct(before, after);
1289
1290 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001291 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 return;
1293 }
reed@google.comabf15c12011-01-18 20:35:51 +00001294
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1296 if (dist < 0) {
1297 dist = -dist;
1298 }
1299
1300 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1301 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1302 SkRotationDirection arcDir;
1303
1304 // now turn before/after into normals
1305 if (sinh > 0) {
1306 before.rotateCCW();
1307 after.rotateCCW();
1308 arcDir = kCW_SkRotationDirection;
1309 } else {
1310 before.rotateCW();
1311 after.rotateCW();
1312 arcDir = kCCW_SkRotationDirection;
1313 }
1314
1315 SkMatrix matrix;
1316 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 matrix.setScale(radius, radius);
1319 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1320 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001321
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001323
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 this->incReserve(count);
1325 // [xx,yy] == pts[0]
1326 this->lineTo(xx, yy);
1327 for (int i = 1; i < count; i += 2) {
1328 this->quadTo(pts[i], pts[i+1]);
1329 }
1330}
1331
1332///////////////////////////////////////////////////////////////////////////////
1333
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001334void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 SkMatrix matrix;
1336
1337 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001338 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339}
1340
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001341void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001342 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001344 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 SkPoint pts[4];
1346 Verb verb;
1347
1348 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001349 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 while ((verb = iter.next(pts)) != kDone_Verb) {
1351 switch (verb) {
1352 case kMove_Verb:
1353 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001354 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1355 injectMoveToIfNeeded(); // In case last contour is closed
1356 this->lineTo(pts[0]);
1357 } else {
1358 this->moveTo(pts[0]);
1359 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 break;
1361 case kLine_Verb:
1362 proc(matrix, &pts[1], &pts[1], 1);
1363 this->lineTo(pts[1]);
1364 break;
1365 case kQuad_Verb:
1366 proc(matrix, &pts[1], &pts[1], 2);
1367 this->quadTo(pts[1], pts[2]);
1368 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001369 case kConic_Verb:
1370 proc(matrix, &pts[1], &pts[1], 2);
1371 this->conicTo(pts[1], pts[2], iter.conicWeight());
1372 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 case kCubic_Verb:
1374 proc(matrix, &pts[1], &pts[1], 3);
1375 this->cubicTo(pts[1], pts[2], pts[3]);
1376 break;
1377 case kClose_Verb:
1378 this->close();
1379 break;
1380 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001381 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001383 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 }
1385}
1386
1387///////////////////////////////////////////////////////////////////////////////
1388
reed@google.com277c3f82013-05-31 15:17:50 +00001389static int pts_in_verb(unsigned verb) {
1390 static const uint8_t gPtsInVerb[] = {
1391 1, // kMove
1392 1, // kLine
1393 2, // kQuad
1394 2, // kConic
1395 3, // kCubic
1396 0, // kClose
1397 0 // kDone
1398 };
1399
1400 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1401 return gPtsInVerb[verb];
1402}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404// ignore the last point of the 1st contour
1405void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001406 int i, vcount = path.fPathRef->countVerbs();
1407 // exit early if the path is empty, or just has a moveTo.
1408 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409 return;
1410 }
1411
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001412 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001414 const uint8_t* verbs = path.fPathRef->verbs();
1415 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001416 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001418 SkASSERT(verbs[~0] == kMove_Verb);
1419 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001420 unsigned v = verbs[~i];
1421 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 if (n == 0) {
1423 break;
1424 }
1425 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001426 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 }
1428
1429 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001430 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 case kLine_Verb:
1432 this->lineTo(pts[-1].fX, pts[-1].fY);
1433 break;
1434 case kQuad_Verb:
1435 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1436 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001437 case kConic_Verb:
1438 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1439 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 case kCubic_Verb:
1441 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1442 pts[-3].fX, pts[-3].fY);
1443 break;
1444 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001445 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 break;
1447 }
reed@google.com277c3f82013-05-31 15:17:50 +00001448 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 }
1450}
1451
reed@google.com63d73742012-01-10 15:33:12 +00001452void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001453 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001455 const SkPoint* pts = src.fPathRef->pointsEnd();
1456 // we will iterator through src's verbs backwards
1457 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1458 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001459 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001460
1461 bool needMove = true;
1462 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001463 while (verbs < verbsEnd) {
1464 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001465 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001466
1467 if (needMove) {
1468 --pts;
1469 this->moveTo(pts->fX, pts->fY);
1470 needMove = false;
1471 }
1472 pts -= n;
1473 switch (v) {
1474 case kMove_Verb:
1475 if (needClose) {
1476 this->close();
1477 needClose = false;
1478 }
1479 needMove = true;
1480 pts += 1; // so we see the point in "if (needMove)" above
1481 break;
1482 case kLine_Verb:
1483 this->lineTo(pts[0]);
1484 break;
1485 case kQuad_Verb:
1486 this->quadTo(pts[1], pts[0]);
1487 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001488 case kConic_Verb:
1489 this->conicTo(pts[1], pts[0], *--conicWeights);
1490 break;
reed@google.com63d73742012-01-10 15:33:12 +00001491 case kCubic_Verb:
1492 this->cubicTo(pts[2], pts[1], pts[0]);
1493 break;
1494 case kClose_Verb:
1495 needClose = true;
1496 break;
1497 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001498 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001499 }
1500 }
1501}
1502
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503///////////////////////////////////////////////////////////////////////////////
1504
1505void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1506 SkMatrix matrix;
1507
1508 matrix.setTranslate(dx, dy);
1509 this->transform(matrix, dst);
1510}
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1513 int level = 2) {
1514 if (--level >= 0) {
1515 SkPoint tmp[7];
1516
1517 SkChopCubicAtHalf(pts, tmp);
1518 subdivide_cubic_to(path, &tmp[0], level);
1519 subdivide_cubic_to(path, &tmp[3], level);
1520 } else {
1521 path->cubicTo(pts[1], pts[2], pts[3]);
1522 }
1523}
1524
1525void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1526 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001527 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 dst = (SkPath*)this;
1529 }
1530
tomhudson@google.com8d430182011-06-06 19:11:19 +00001531 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 SkPath tmp;
1533 tmp.fFillType = fFillType;
1534
1535 SkPath::Iter iter(*this, false);
1536 SkPoint pts[4];
1537 SkPath::Verb verb;
1538
reed@google.com4a3b7142012-05-16 17:16:46 +00001539 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 switch (verb) {
1541 case kMove_Verb:
1542 tmp.moveTo(pts[0]);
1543 break;
1544 case kLine_Verb:
1545 tmp.lineTo(pts[1]);
1546 break;
1547 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001548 // promote the quad to a conic
1549 tmp.conicTo(pts[1], pts[2],
1550 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001552 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001553 tmp.conicTo(pts[1], pts[2],
1554 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001555 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 case kCubic_Verb:
1557 subdivide_cubic_to(&tmp, pts);
1558 break;
1559 case kClose_Verb:
1560 tmp.close();
1561 break;
1562 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001563 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 break;
1565 }
1566 }
1567
1568 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 SkPathRef::Editor ed(&dst->fPathRef);
1570 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001571 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001573 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1574
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001577 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001578 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001580
reed026beb52015-06-10 14:23:15 -07001581 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1582 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001583 } else {
1584 SkScalar det2x2 =
1585 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1586 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1587 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001588 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1589 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001590 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001591 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001592 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001593 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001594 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001595 }
1596 }
1597
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 SkDEBUGCODE(dst->validate();)
1599 }
1600}
1601
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602///////////////////////////////////////////////////////////////////////////////
1603///////////////////////////////////////////////////////////////////////////////
1604
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001606 kEmptyContour_SegmentState, // The current contour is empty. We may be
1607 // starting processing or we may have just
1608 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001609 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1610 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1611 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612};
1613
1614SkPath::Iter::Iter() {
1615#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001616 fPts = nullptr;
1617 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001619 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001620 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621#endif
1622 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001623 fVerbs = nullptr;
1624 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 fNeedClose = false;
1626}
1627
1628SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1629 this->setPath(path, forceClose);
1630}
1631
1632void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001633 fPts = path.fPathRef->points();
1634 fVerbs = path.fPathRef->verbs();
1635 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001636 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001637 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001638 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 fForceClose = SkToU8(forceClose);
1640 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001641 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642}
1643
1644bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001645 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 return false;
1647 }
1648 if (fForceClose) {
1649 return true;
1650 }
1651
1652 const uint8_t* verbs = fVerbs;
1653 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001654
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001655 if (kMove_Verb == *(verbs - 1)) {
1656 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657 }
1658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001659 while (verbs > stop) {
1660 // verbs points one beyond the current verb, decrement first.
1661 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 if (kMove_Verb == v) {
1663 break;
1664 }
1665 if (kClose_Verb == v) {
1666 return true;
1667 }
1668 }
1669 return false;
1670}
1671
1672SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001673 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001675 // A special case: if both points are NaN, SkPoint::operation== returns
1676 // false, but the iterator expects that they are treated as the same.
1677 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001678 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1679 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001680 return kClose_Verb;
1681 }
1682
reed@google.com9e25dbf2012-05-15 17:05:38 +00001683 pts[0] = fLastPt;
1684 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001685 fLastPt = fMoveTo;
1686 fCloseLine = true;
1687 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001688 } else {
1689 pts[0] = fMoveTo;
1690 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692}
1693
reed@google.com9e25dbf2012-05-15 17:05:38 +00001694const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695 if (fSegmentState == kAfterMove_SegmentState) {
1696 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001698 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001700 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1701 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001702 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704}
1705
caryclarke8c56662015-07-14 11:19:26 -07001706void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001707 // We need to step over anything that will not move the current draw point
1708 // forward before the next move is seen
1709 const uint8_t* lastMoveVerb = 0;
1710 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001711 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001712 SkPoint lastPt = fLastPt;
1713 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001714 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001715 switch (verb) {
1716 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001717 // Keep a record of this most recent move
1718 lastMoveVerb = fVerbs;
1719 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001720 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001721 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001723 fPts++;
1724 break;
1725
1726 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001727 // A close when we are in a segment is always valid except when it
1728 // follows a move which follows a segment.
1729 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 return;
1731 }
1732 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001734 break;
1735
1736 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001737 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 if (lastMoveVerb) {
1739 fVerbs = lastMoveVerb;
1740 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001741 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001742 return;
1743 }
1744 return;
1745 }
1746 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748 fPts++;
1749 break;
1750
reed@google.com277c3f82013-05-31 15:17:50 +00001751 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001752 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001753 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001754 if (lastMoveVerb) {
1755 fVerbs = lastMoveVerb;
1756 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001757 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001758 return;
1759 }
1760 return;
1761 }
1762 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001764 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001765 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766 break;
1767
1768 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001769 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001770 if (lastMoveVerb) {
1771 fVerbs = lastMoveVerb;
1772 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001773 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 return;
1775 }
1776 return;
1777 }
1778 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001779 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001780 fPts += 3;
1781 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001782
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001784 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001785 }
1786 }
1787}
1788
reed@google.com4a3b7142012-05-16 17:16:46 +00001789SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001791
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 // Close the curve if requested and if there is some curve to close
1794 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001795 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 return kLine_Verb;
1797 }
1798 fNeedClose = false;
1799 return kClose_Verb;
1800 }
1801 return kDone_Verb;
1802 }
1803
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001804 // fVerbs is one beyond the current verb, decrement first
1805 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001806 const SkPoint* SK_RESTRICT srcPts = fPts;
1807 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808
1809 switch (verb) {
1810 case kMove_Verb:
1811 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001812 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 verb = this->autoClose(pts);
1814 if (verb == kClose_Verb) {
1815 fNeedClose = false;
1816 }
1817 return (Verb)verb;
1818 }
1819 if (fVerbs == fVerbStop) { // might be a trailing moveto
1820 return kDone_Verb;
1821 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001822 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001823 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001825 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 fNeedClose = fForceClose;
1828 break;
1829 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001830 pts[0] = this->cons_moveTo();
1831 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 fLastPt = srcPts[0];
1833 fCloseLine = false;
1834 srcPts += 1;
1835 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001836 case kConic_Verb:
1837 fConicWeights += 1;
1838 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001840 pts[0] = this->cons_moveTo();
1841 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 fLastPt = srcPts[1];
1843 srcPts += 2;
1844 break;
1845 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001846 pts[0] = this->cons_moveTo();
1847 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 fLastPt = srcPts[2];
1849 srcPts += 3;
1850 break;
1851 case kClose_Verb:
1852 verb = this->autoClose(pts);
1853 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001854 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 } else {
1856 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001857 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 break;
1861 }
1862 fPts = srcPts;
1863 return (Verb)verb;
1864}
1865
1866///////////////////////////////////////////////////////////////////////////////
1867
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001869 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870*/
1871
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001872size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873 SkDEBUGCODE(this->validate();)
1874
halcanary96fcdcc2015-08-27 07:41:13 -07001875 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001876 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001877 return SkAlign4(byteCount);
1878 }
1879
1880 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001881
robertphillips@google.com466310d2013-12-03 16:43:54 +00001882 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001883 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001884 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001885 (fIsVolatile << kIsVolatile_SerializationShift) |
1886 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001887
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001888 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001889
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001890 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001891
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001892 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001893 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894}
1895
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001896size_t SkPath::readFromMemory(const void* storage, size_t length) {
1897 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001898
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001899 int32_t packed;
1900 if (!buffer.readS32(&packed)) {
1901 return 0;
1902 }
1903
reed8f086022015-06-11 14:22:19 -07001904 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001905
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001906 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1907 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001908 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001909 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001910 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001911
reed8f086022015-06-11 14:22:19 -07001912 // compatibility check
1913 if (version < kPathPrivFirstDirection_Version) {
1914 switch (dir) { // old values
1915 case 0:
1916 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1917 break;
1918 case 1:
1919 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1920 break;
1921 case 2:
1922 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1923 break;
1924 default:
1925 SkASSERT(false);
1926 }
1927 } else {
1928 fFirstDirection = dir;
1929 }
1930
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001931 size_t sizeRead = 0;
1932 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001933 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001934 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001935 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001936 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001937 } else if (pathRef) {
halcanary96fcdcc2015-08-27 07:41:13 -07001938 // If the buffer is not valid, pathRef should be nullptr
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001939 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001940 }
1941 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942}
1943
1944///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945
reede05fed02014-12-15 07:59:53 -08001946#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001947#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001948
reed@google.com51bbe752013-01-17 22:07:50 +00001949static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001950 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001951 str->append(label);
1952 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001953
reed@google.com51bbe752013-01-17 22:07:50 +00001954 const SkScalar* values = &pts[0].fX;
1955 count *= 2;
1956
1957 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001958 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001959 if (i < count - 1) {
1960 str->append(", ");
1961 }
1962 }
reed@google.com277c3f82013-05-31 15:17:50 +00001963 if (conicWeight >= 0) {
1964 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001965 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001966 }
caryclark08fa28c2014-10-23 13:08:56 -07001967 str->append(");");
reede05fed02014-12-15 07:59:53 -08001968 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001969 str->append(" // ");
1970 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001971 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001972 if (i < count - 1) {
1973 str->append(", ");
1974 }
1975 }
1976 if (conicWeight >= 0) {
1977 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001978 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001979 }
1980 }
1981 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001982}
1983
caryclarke9562592014-09-15 09:26:09 -07001984void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001985 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 Iter iter(*this, forceClose);
1987 SkPoint pts[4];
1988 Verb verb;
1989
caryclark66a5d8b2014-06-24 08:30:15 -07001990 if (!wStream) {
1991 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1992 }
reed@google.com51bbe752013-01-17 22:07:50 +00001993 SkString builder;
1994
reed@google.com4a3b7142012-05-16 17:16:46 +00001995 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 switch (verb) {
1997 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001998 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 break;
2000 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002001 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 break;
2003 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002004 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002006 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002007 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002008 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002010 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011 break;
2012 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002013 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 break;
2015 default:
2016 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2017 verb = kDone_Verb; // stop the loop
2018 break;
2019 }
caryclark1049f122015-04-20 08:31:59 -07002020 if (!wStream && builder.size()) {
2021 SkDebugf("%s", builder.c_str());
2022 builder.reset();
2023 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 }
caryclark66a5d8b2014-06-24 08:30:15 -07002025 if (wStream) {
2026 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002027 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028}
2029
reed@android.come522ca52009-11-23 20:10:41 +00002030void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002031 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002032}
2033
2034void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002035 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002036}
2037
2038#ifdef SK_DEBUG
2039void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002040 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002041
djsollen@google.com077348c2012-10-22 20:23:32 +00002042#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002043 if (!fBoundsIsDirty) {
2044 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002045
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002046 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002047 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002048
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002049 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002050 // if we're empty, fBounds may be empty but translated, so we can't
2051 // necessarily compare to bounds directly
2052 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2053 // be [2, 2, 2, 2]
2054 SkASSERT(bounds.isEmpty());
2055 SkASSERT(fBounds.isEmpty());
2056 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002057 if (bounds.isEmpty()) {
2058 SkASSERT(fBounds.isEmpty());
2059 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002060 if (!fBounds.isEmpty()) {
2061 SkASSERT(fBounds.contains(bounds));
2062 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002063 }
reed@android.come522ca52009-11-23 20:10:41 +00002064 }
2065 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002066#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002067}
djsollen@google.com077348c2012-10-22 20:23:32 +00002068#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002069
reed@google.com04863fa2011-05-15 04:08:24 +00002070///////////////////////////////////////////////////////////////////////////////
2071
reed@google.com0b7b9822011-05-16 12:29:27 +00002072static int sign(SkScalar x) { return x < 0; }
2073#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002074
robertphillipsc506e302014-09-16 09:43:31 -07002075enum DirChange {
2076 kLeft_DirChange,
2077 kRight_DirChange,
2078 kStraight_DirChange,
2079 kBackwards_DirChange,
2080
2081 kInvalid_DirChange
2082};
2083
2084
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002085static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002086 // The error epsilon was empirically derived; worse case round rects
2087 // with a mid point outset by 2x float epsilon in tests had an error
2088 // of 12.
2089 const int epsilon = 16;
2090 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2091 return false;
2092 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002093 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002094 int aBits = SkFloatAs2sCompliment(compA);
2095 int bBits = SkFloatAs2sCompliment(compB);
2096 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002097}
2098
caryclarkb4216502015-03-02 13:02:34 -08002099static bool approximately_zero_when_compared_to(double x, double y) {
2100 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002101}
2102
caryclarkb4216502015-03-02 13:02:34 -08002103
reed@google.com04863fa2011-05-15 04:08:24 +00002104// only valid for a single contour
2105struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002106 Convexicator()
2107 : fPtCount(0)
2108 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002109 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002110 , fIsFinite(true)
2111 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002112 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002113 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002114 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002115 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002116 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002117 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002118
2119 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002120 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002121 }
2122
2123 SkPath::Convexity getConvexity() const { return fConvexity; }
2124
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002125 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002126 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002127
reed@google.com04863fa2011-05-15 04:08:24 +00002128 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002129 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002130 return;
2131 }
2132
2133 if (0 == fPtCount) {
2134 fCurrPt = pt;
2135 ++fPtCount;
2136 } else {
2137 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002138 SkScalar lengthSqd = vec.lengthSqd();
2139 if (!SkScalarIsFinite(lengthSqd)) {
2140 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002141 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002142 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002143 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002144 fCurrPt = pt;
2145 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002146 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002147 } else {
2148 SkASSERT(fPtCount > 2);
2149 this->addVec(vec);
2150 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002151
reed@google.com85b6e392011-05-15 20:25:17 +00002152 int sx = sign(vec.fX);
2153 int sy = sign(vec.fY);
2154 fDx += (sx != fSx);
2155 fDy += (sy != fSy);
2156 fSx = sx;
2157 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002158
reed@google.com85b6e392011-05-15 20:25:17 +00002159 if (fDx > 3 || fDy > 3) {
2160 fConvexity = SkPath::kConcave_Convexity;
2161 }
reed@google.com04863fa2011-05-15 04:08:24 +00002162 }
2163 }
2164 }
2165
2166 void close() {
2167 if (fPtCount > 2) {
2168 this->addVec(fFirstVec);
2169 }
2170 }
2171
caryclarkb4216502015-03-02 13:02:34 -08002172 DirChange directionChange(const SkVector& curVec) {
2173 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2174
2175 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2176 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2177 largest = SkTMax(largest, -smallest);
2178
2179 if (!almost_equal(largest, largest + cross)) {
2180 int sign = SkScalarSignAsInt(cross);
2181 if (sign) {
2182 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2183 }
2184 }
2185
2186 if (cross) {
2187 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2188 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2189 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2190 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2191 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2192 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2193 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2194 if (sign) {
2195 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2196 }
2197 }
2198 }
2199
2200 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2201 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2202 fLastVec.dot(curVec) < 0.0f) {
2203 return kBackwards_DirChange;
2204 }
2205
2206 return kStraight_DirChange;
2207 }
2208
2209
caryclarkd3d1a982014-12-08 04:57:38 -08002210 bool isFinite() const {
2211 return fIsFinite;
2212 }
2213
caryclark5ccef572015-03-02 10:07:56 -08002214 void setCurve(bool isCurve) {
2215 fIsCurve = isCurve;
2216 }
2217
reed@google.com04863fa2011-05-15 04:08:24 +00002218private:
2219 void addVec(const SkVector& vec) {
2220 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002221 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002222 switch (dir) {
2223 case kLeft_DirChange: // fall through
2224 case kRight_DirChange:
2225 if (kInvalid_DirChange == fExpectedDir) {
2226 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002227 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2228 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002229 } else if (dir != fExpectedDir) {
2230 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002231 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002232 }
2233 fLastVec = vec;
2234 break;
2235 case kStraight_DirChange:
2236 break;
2237 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002238 if (fIsCurve) {
2239 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002240 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002241 }
robertphillipsc506e302014-09-16 09:43:31 -07002242 fLastVec = vec;
2243 break;
2244 case kInvalid_DirChange:
2245 SkFAIL("Use of invalid direction change flag");
2246 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002247 }
2248 }
2249
caryclarkb4216502015-03-02 13:02:34 -08002250 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002251 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002252 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002253 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2254 // value with the current vec is deemed to be of a significant value.
2255 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002256 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002257 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002258 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002259 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002260 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002261 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002262 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002263};
2264
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002265SkPath::Convexity SkPath::internalGetConvexity() const {
2266 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002267 SkPoint pts[4];
2268 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002269 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002270
2271 int contourCount = 0;
2272 int count;
2273 Convexicator state;
2274
caryclarkd3d1a982014-12-08 04:57:38 -08002275 if (!isFinite()) {
2276 return kUnknown_Convexity;
2277 }
caryclarke8c56662015-07-14 11:19:26 -07002278 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002279 switch (verb) {
2280 case kMove_Verb:
2281 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002283 return kConcave_Convexity;
2284 }
2285 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002286 // fall through
2287 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002288 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002289 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002290 break;
caryclark5ccef572015-03-02 10:07:56 -08002291 case kQuad_Verb:
2292 // fall through
2293 case kConic_Verb:
2294 // fall through
2295 case kCubic_Verb:
2296 count = 2 + (kCubic_Verb == verb);
2297 // As an additional enhancement, this could set curve true only
2298 // if the curve is nonlinear
2299 state.setCurve(true);
2300 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002301 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002302 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002303 state.close();
2304 count = 0;
2305 break;
2306 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002307 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002308 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002309 return kConcave_Convexity;
2310 }
2311
2312 for (int i = 1; i <= count; i++) {
2313 state.addPt(pts[i]);
2314 }
2315 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002316 if (!state.isFinite()) {
2317 return kUnknown_Convexity;
2318 }
reed@google.com04863fa2011-05-15 04:08:24 +00002319 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002320 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002321 return kConcave_Convexity;
2322 }
2323 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002324 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002325 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2326 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002327 }
2328 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002329}
reed@google.com69a99432012-01-10 18:00:10 +00002330
2331///////////////////////////////////////////////////////////////////////////////
2332
2333class ContourIter {
2334public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002335 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002336
2337 bool done() const { return fDone; }
2338 // if !done() then these may be called
2339 int count() const { return fCurrPtCount; }
2340 const SkPoint* pts() const { return fCurrPt; }
2341 void next();
2342
2343private:
2344 int fCurrPtCount;
2345 const SkPoint* fCurrPt;
2346 const uint8_t* fCurrVerb;
2347 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002348 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002349 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002350 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002351};
2352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002353ContourIter::ContourIter(const SkPathRef& pathRef) {
2354 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002355 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002356 fCurrPt = pathRef.points();
2357 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002358 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002359 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002360 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002361 this->next();
2362}
2363
2364void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002365 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002366 fDone = true;
2367 }
2368 if (fDone) {
2369 return;
2370 }
2371
2372 // skip pts of prev contour
2373 fCurrPt += fCurrPtCount;
2374
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002375 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002376 int ptCount = 1; // moveTo
2377 const uint8_t* verbs = fCurrVerb;
2378
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002379 for (--verbs; verbs > fStopVerbs; --verbs) {
2380 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002381 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002382 goto CONTOUR_END;
2383 case SkPath::kLine_Verb:
2384 ptCount += 1;
2385 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002386 case SkPath::kConic_Verb:
2387 fCurrConicWeight += 1;
2388 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002389 case SkPath::kQuad_Verb:
2390 ptCount += 2;
2391 break;
2392 case SkPath::kCubic_Verb:
2393 ptCount += 3;
2394 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002395 case SkPath::kClose_Verb:
2396 break;
2397 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002398 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002399 break;
2400 }
2401 }
2402CONTOUR_END:
2403 fCurrPtCount = ptCount;
2404 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002405 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002406}
2407
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002408// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002409static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002410 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2411 // We may get 0 when the above subtracts underflow. We expect this to be
2412 // very rare and lazily promote to double.
2413 if (0 == cross) {
2414 double p0x = SkScalarToDouble(p0.fX);
2415 double p0y = SkScalarToDouble(p0.fY);
2416
2417 double p1x = SkScalarToDouble(p1.fX);
2418 double p1y = SkScalarToDouble(p1.fY);
2419
2420 double p2x = SkScalarToDouble(p2.fX);
2421 double p2y = SkScalarToDouble(p2.fY);
2422
2423 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2424 (p1y - p0y) * (p2x - p0x));
2425
2426 }
2427 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002428}
2429
reed@google.comc1ea60a2012-01-31 15:15:36 +00002430// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002431static int find_max_y(const SkPoint pts[], int count) {
2432 SkASSERT(count > 0);
2433 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002434 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002435 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002436 SkScalar y = pts[i].fY;
2437 if (y > max) {
2438 max = y;
2439 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002440 }
2441 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002442 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002443}
2444
reed@google.comcabaf1d2012-01-11 21:03:05 +00002445static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2446 int i = index;
2447 for (;;) {
2448 i = (i + inc) % n;
2449 if (i == index) { // we wrapped around, so abort
2450 break;
2451 }
2452 if (pts[index] != pts[i]) { // found a different point, success!
2453 break;
2454 }
2455 }
2456 return i;
2457}
2458
reed@google.comc1ea60a2012-01-31 15:15:36 +00002459/**
2460 * Starting at index, and moving forward (incrementing), find the xmin and
2461 * xmax of the contiguous points that have the same Y.
2462 */
2463static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2464 int* maxIndexPtr) {
2465 const SkScalar y = pts[index].fY;
2466 SkScalar min = pts[index].fX;
2467 SkScalar max = min;
2468 int minIndex = index;
2469 int maxIndex = index;
2470 for (int i = index + 1; i < n; ++i) {
2471 if (pts[i].fY != y) {
2472 break;
2473 }
2474 SkScalar x = pts[i].fX;
2475 if (x < min) {
2476 min = x;
2477 minIndex = i;
2478 } else if (x > max) {
2479 max = x;
2480 maxIndex = i;
2481 }
2482 }
2483 *maxIndexPtr = maxIndex;
2484 return minIndex;
2485}
2486
reed026beb52015-06-10 14:23:15 -07002487static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2488 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002489}
2490
reed@google.comac8543f2012-01-30 20:51:25 +00002491/*
2492 * We loop through all contours, and keep the computed cross-product of the
2493 * contour that contained the global y-max. If we just look at the first
2494 * contour, we may find one that is wound the opposite way (correctly) since
2495 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2496 * that is outer most (or at least has the global y-max) before we can consider
2497 * its cross product.
2498 */
reed026beb52015-06-10 14:23:15 -07002499bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002500 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2501 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002502 return true;
2503 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002504
2505 // don't want to pay the cost for computing this if it
2506 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002507 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2508 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002509 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002510 return false;
2511 }
reed@google.com69a99432012-01-10 18:00:10 +00002512
reed026beb52015-06-10 14:23:15 -07002513 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002514
reed@google.comac8543f2012-01-30 20:51:25 +00002515 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002516 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002517 SkScalar ymaxCross = 0;
2518
reed@google.com69a99432012-01-10 18:00:10 +00002519 for (; !iter.done(); iter.next()) {
2520 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002521 if (n < 3) {
2522 continue;
2523 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002524
reed@google.comcabaf1d2012-01-11 21:03:05 +00002525 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002526 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002527 int index = find_max_y(pts, n);
2528 if (pts[index].fY < ymax) {
2529 continue;
2530 }
2531
2532 // If there is more than 1 distinct point at the y-max, we take the
2533 // x-min and x-max of them and just subtract to compute the dir.
2534 if (pts[(index + 1) % n].fY == pts[index].fY) {
2535 int maxIndex;
2536 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2537 if (minIndex == maxIndex) {
2538 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002539 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002540 SkASSERT(pts[minIndex].fY == pts[index].fY);
2541 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2542 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2543 // we just subtract the indices, and let that auto-convert to
2544 // SkScalar, since we just want - or + to signal the direction.
2545 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002546 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002547 TRY_CROSSPROD:
2548 // Find a next and prev index to use for the cross-product test,
2549 // but we try to find pts that form non-zero vectors from pts[index]
2550 //
2551 // Its possible that we can't find two non-degenerate vectors, so
2552 // we have to guard our search (e.g. all the pts could be in the
2553 // same place).
2554
2555 // we pass n - 1 instead of -1 so we don't foul up % operator by
2556 // passing it a negative LH argument.
2557 int prev = find_diff_pt(pts, index, n, n - 1);
2558 if (prev == index) {
2559 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002560 continue;
2561 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002562 int next = find_diff_pt(pts, index, n, 1);
2563 SkASSERT(next != index);
2564 cross = cross_prod(pts[prev], pts[index], pts[next]);
2565 // if we get a zero and the points are horizontal, then we look at the spread in
2566 // x-direction. We really should continue to walk away from the degeneracy until
2567 // there is a divergence.
2568 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2569 // construct the subtract so we get the correct Direction below
2570 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002571 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002572 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002573
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002574 if (cross) {
2575 // record our best guess so far
2576 ymax = pts[index].fY;
2577 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002578 }
2579 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002580 if (ymaxCross) {
2581 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002582 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002583 return true;
2584 } else {
2585 return false;
2586 }
reed@google.comac8543f2012-01-30 20:51:25 +00002587}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002588
2589///////////////////////////////////////////////////////////////////////////////
2590
2591static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2592 SkScalar D, SkScalar t) {
2593 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2594}
2595
2596static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2597 SkScalar t) {
2598 SkScalar A = c3 + 3*(c1 - c2) - c0;
2599 SkScalar B = 3*(c2 - c1 - c1 + c0);
2600 SkScalar C = 3*(c1 - c0);
2601 SkScalar D = c0;
2602 return eval_cubic_coeff(A, B, C, D, t);
2603}
2604
2605/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2606 t value such that cubic(t) = target
2607 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002608static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002609 SkScalar target, SkScalar* t) {
2610 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2611 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002612
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002613 SkScalar D = c0 - target;
2614 SkScalar A = c3 + 3*(c1 - c2) - c0;
2615 SkScalar B = 3*(c2 - c1 - c1 + c0);
2616 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002617
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002618 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2619 SkScalar minT = 0;
2620 SkScalar maxT = SK_Scalar1;
2621 SkScalar mid;
2622 int i;
2623 for (i = 0; i < 16; i++) {
2624 mid = SkScalarAve(minT, maxT);
2625 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2626 if (delta < 0) {
2627 minT = mid;
2628 delta = -delta;
2629 } else {
2630 maxT = mid;
2631 }
2632 if (delta < TOLERANCE) {
2633 break;
2634 }
2635 }
2636 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002637}
2638
2639template <size_t N> static void find_minmax(const SkPoint pts[],
2640 SkScalar* minPtr, SkScalar* maxPtr) {
2641 SkScalar min, max;
2642 min = max = pts[0].fX;
2643 for (size_t i = 1; i < N; ++i) {
2644 min = SkMinScalar(min, pts[i].fX);
2645 max = SkMaxScalar(max, pts[i].fX);
2646 }
2647 *minPtr = min;
2648 *maxPtr = max;
2649}
2650
2651static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2652 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002653
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002654 int dir = 1;
2655 if (pts[0].fY > pts[3].fY) {
2656 storage[0] = pts[3];
2657 storage[1] = pts[2];
2658 storage[2] = pts[1];
2659 storage[3] = pts[0];
2660 pts = storage;
2661 dir = -1;
2662 }
2663 if (y < pts[0].fY || y >= pts[3].fY) {
2664 return 0;
2665 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002666
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002667 // quickreject or quickaccept
2668 SkScalar min, max;
2669 find_minmax<4>(pts, &min, &max);
2670 if (x < min) {
2671 return 0;
2672 }
2673 if (x > max) {
2674 return dir;
2675 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002676
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002677 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002678 SkScalar t;
2679 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2680 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 +00002681 return xt < x ? dir : 0;
2682}
2683
2684static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2685 SkPoint dst[10];
2686 int n = SkChopCubicAtYExtrema(pts, dst);
2687 int w = 0;
2688 for (int i = 0; i <= n; ++i) {
2689 w += winding_mono_cubic(&dst[i * 3], x, y);
2690 }
2691 return w;
2692}
2693
2694static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2695 SkScalar y0 = pts[0].fY;
2696 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002697
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002698 int dir = 1;
2699 if (y0 > y2) {
2700 SkTSwap(y0, y2);
2701 dir = -1;
2702 }
2703 if (y < y0 || y >= y2) {
2704 return 0;
2705 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002706
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002707 // bounds check on X (not required. is it faster?)
2708#if 0
2709 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2710 return 0;
2711 }
2712#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002713
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714 SkScalar roots[2];
2715 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2716 2 * (pts[1].fY - pts[0].fY),
2717 pts[0].fY - y,
2718 roots);
2719 SkASSERT(n <= 1);
2720 SkScalar xt;
2721 if (0 == n) {
2722 SkScalar mid = SkScalarAve(y0, y2);
2723 // Need [0] and [2] if dir == 1
2724 // and [2] and [0] if dir == -1
2725 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2726 } else {
2727 SkScalar t = roots[0];
2728 SkScalar C = pts[0].fX;
2729 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2730 SkScalar B = 2 * (pts[1].fX - C);
2731 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2732 }
2733 return xt < x ? dir : 0;
2734}
2735
2736static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2737 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2738 if (y0 == y1) {
2739 return true;
2740 }
2741 if (y0 < y1) {
2742 return y1 <= y2;
2743 } else {
2744 return y1 >= y2;
2745 }
2746}
2747
2748static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2749 SkPoint dst[5];
2750 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002751
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2753 n = SkChopQuadAtYExtrema(pts, dst);
2754 pts = dst;
2755 }
2756 int w = winding_mono_quad(pts, x, y);
2757 if (n > 0) {
2758 w += winding_mono_quad(&pts[2], x, y);
2759 }
2760 return w;
2761}
2762
2763static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2764 SkScalar x0 = pts[0].fX;
2765 SkScalar y0 = pts[0].fY;
2766 SkScalar x1 = pts[1].fX;
2767 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771 int dir = 1;
2772 if (y0 > y1) {
2773 SkTSwap(y0, y1);
2774 dir = -1;
2775 }
2776 if (y < y0 || y >= y1) {
2777 return 0;
2778 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2781 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 if (SkScalarSignAsInt(cross) == dir) {
2784 dir = 0;
2785 }
2786 return dir;
2787}
2788
reed@google.com4db592c2013-10-30 17:39:43 +00002789static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2790 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2791}
2792
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793bool SkPath::contains(SkScalar x, SkScalar y) const {
2794 bool isInverse = this->isInverseFillType();
2795 if (this->isEmpty()) {
2796 return isInverse;
2797 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002798
reed@google.com4db592c2013-10-30 17:39:43 +00002799 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 return isInverse;
2801 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002802
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803 SkPath::Iter iter(*this, true);
2804 bool done = false;
2805 int w = 0;
2806 do {
2807 SkPoint pts[4];
2808 switch (iter.next(pts, false)) {
2809 case SkPath::kMove_Verb:
2810 case SkPath::kClose_Verb:
2811 break;
2812 case SkPath::kLine_Verb:
2813 w += winding_line(pts, x, y);
2814 break;
2815 case SkPath::kQuad_Verb:
2816 w += winding_quad(pts, x, y);
2817 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002818 case SkPath::kConic_Verb:
2819 SkASSERT(0);
2820 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002821 case SkPath::kCubic_Verb:
2822 w += winding_cubic(pts, x, y);
2823 break;
2824 case SkPath::kDone_Verb:
2825 done = true;
2826 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002827 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002829
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 switch (this->getFillType()) {
2831 case SkPath::kEvenOdd_FillType:
2832 case SkPath::kInverseEvenOdd_FillType:
2833 w &= 1;
2834 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002835 default:
2836 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 }
2838 return SkToBool(w);
2839}
fmalitaaa0df4e2015-12-01 09:13:23 -08002840
2841int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
2842 SkScalar w, SkPoint pts[], int pow2) {
2843 const SkConic conic(p0, p1, p2, w);
2844 return conic.chopIntoQuadsPOW2(pts, pow2);
2845}