blob: 27b7283263a059bf4d018bd8f2abb6b0358d6510 [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"
humper@google.com75e3ca12013-04-08 21:44:11 +000012#include "SkPath.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"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
reed1b28a3a2014-12-17 14:42:09 -080017// These two should be removed once we fix any gpu bugs, and then
18// just move them into skia_for_chromium_defines.gypi
reed220f9262014-12-17 08:21:04 -080019#define SK_SUPPORT_LEGACY_ADDOVAL
reed1b28a3a2014-12-17 14:42:09 -080020#define SK_SUPPORT_LEGACY_ADDRRECT
reed220f9262014-12-17 08:21:04 -080021
reed@android.com8a1c16f2008-12-17 15:59:43 +000022////////////////////////////////////////////////////////////////////////////
23
reed@google.com3563c9e2011-11-14 19:34:57 +000024/**
25 * Path.bounds is defined to be the bounds of all the control points.
26 * If we called bounds.join(r) we would skip r if r was empty, which breaks
27 * our promise. Hence we have a custom joiner that doesn't look at emptiness
28 */
29static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
30 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
31 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
32 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
33 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
34}
35
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000036static bool is_degenerate(const SkPath& path) {
37 SkPath::Iter iter(path, false);
38 SkPoint pts[4];
39 return SkPath::kDone_Verb == iter.next(pts);
40}
41
bsalomon@google.com30c174b2012-11-13 14:36:42 +000042class SkAutoDisableDirectionCheck {
43public:
44 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
45 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
46 }
47
48 ~SkAutoDisableDirectionCheck() {
49 fPath->fDirection = fSaved;
50 }
51
52private:
53 SkPath* fPath;
54 SkPath::Direction fSaved;
55};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000056#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058/* This guy's constructor/destructor bracket a path editing operation. It is
59 used when we know the bounds of the amount we are going to add to the path
60 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000061
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000063 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000064 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000065
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000066 It also notes if the path was originally degenerate, and if so, sets
67 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000068 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 */
70class SkAutoPathBoundsUpdate {
71public:
72 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
73 this->init(path);
74 }
75
76 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
77 SkScalar right, SkScalar bottom) {
78 fRect.set(left, top, right, bottom);
79 this->init(path);
80 }
reed@google.comabf15c12011-01-18 20:35:51 +000081
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000083 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
84 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000085 if (fEmpty || fHasValidBounds) {
86 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 }
88 }
reed@google.comabf15c12011-01-18 20:35:51 +000089
reed@android.com8a1c16f2008-12-17 15:59:43 +000090private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 SkPath* fPath;
92 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000094 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000095 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com6b82d1a2009-06-03 02:35:01 +000097 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 // Cannot use fRect for our bounds unless we know it is sorted
99 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000101 // Mark the path's bounds as dirty if (1) they are, or (2) the path
102 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000103 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 if (fHasValidBounds && !fEmpty) {
106 joinNoEmptyChecks(&fRect, fPath->getBounds());
107 }
108 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 }
110};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000111#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113////////////////////////////////////////////////////////////////////////////
114
115/*
116 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
119
120 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000121 1. If we encounter degenerate segments, remove them
122 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
123 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
124 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125*/
126
127////////////////////////////////////////////////////////////////////////////
128
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000129// flag to require a moveTo if we begin with something else, like lineTo etc.
130#define INITIAL_LASTMOVETOINDEX_VALUE ~0
131
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000132SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000133 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000134#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000135 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136#endif
137{
138 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700139 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140}
141
142void SkPath::resetFields() {
143 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000144 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000146 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000147 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000148
149 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
150 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000151}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000154 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000156#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000157 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160}
161
162SkPath::~SkPath() {
163 SkDEBUGCODE(this->validate();)
164}
165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166SkPath& SkPath::operator=(const SkPath& that) {
167 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 if (this != &that) {
170 fPathRef.reset(SkRef(that.fPathRef.get()));
171 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000172#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000173 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000174#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 }
176 SkDEBUGCODE(this->validate();)
177 return *this;
178}
179
bungeman@google.coma5809a32013-06-21 15:13:34 +0000180void SkPath::copyFields(const SkPath& that) {
181 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000182 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 fConvexity = that.fConvexity;
185 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700186 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187}
188
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000189bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000190 // note: don't need to look at isConvex or bounds, since just comparing the
191 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000192 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000193 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000194}
195
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196void SkPath::swap(SkPath& that) {
197 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 if (this != &that) {
200 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000201 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
204 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700205 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000206#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000207 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
208#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 }
210}
211
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000212static inline bool check_edge_against_rect(const SkPoint& p0,
213 const SkPoint& p1,
214 const SkRect& rect,
215 SkPath::Direction dir) {
216 const SkPoint* edgeBegin;
217 SkVector v;
218 if (SkPath::kCW_Direction == dir) {
219 v = p1 - p0;
220 edgeBegin = &p0;
221 } else {
222 v = p0 - p1;
223 edgeBegin = &p1;
224 }
225 if (v.fX || v.fY) {
226 // check the cross product of v with the vec from edgeBegin to each rect corner
227 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
228 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
229 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
230 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
231 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
232 return false;
233 }
234 }
235 return true;
236}
237
238bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
239 // This only handles non-degenerate convex paths currently.
240 if (kConvex_Convexity != this->getConvexity()) {
241 return false;
242 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000243
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000244 Direction direction;
245 if (!this->cheapComputeDirection(&direction)) {
246 return false;
247 }
248
249 SkPoint firstPt;
250 SkPoint prevPt;
251 RawIter iter(*this);
252 SkPath::Verb verb;
253 SkPoint pts[4];
254 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkDEBUGCODE(int segmentCount = 0;)
256 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257
258 while ((verb = iter.next(pts)) != kDone_Verb) {
259 int nextPt = -1;
260 switch (verb) {
261 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 SkDEBUGCODE(++moveCnt);
264 firstPt = prevPt = pts[0];
265 break;
266 case kLine_Verb:
267 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000268 SkASSERT(moveCnt && !closeCount);
269 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 break;
271 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000272 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000273 SkASSERT(moveCnt && !closeCount);
274 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 nextPt = 2;
276 break;
277 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkASSERT(moveCnt && !closeCount);
279 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 nextPt = 3;
281 break;
282 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000283 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000284 break;
285 default:
286 SkDEBUGFAIL("unknown verb");
287 }
288 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800289 if (SkPath::kConic_Verb == verb) {
290 SkConic orig;
291 orig.set(pts, iter.conicWeight());
292 SkPoint quadPts[5];
293 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
294 SK_ALWAYSBREAK(2 == count);
295
296 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
297 return false;
298 }
299 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
300 return false;
301 }
302 } else {
303 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
304 return false;
305 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000306 }
307 prevPt = pts[nextPt];
308 }
309 }
310
311 return check_edge_against_rect(prevPt, firstPt, rect, direction);
312}
313
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000314uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000315 uint32_t genID = fPathRef->genID();
316#ifdef SK_BUILD_FOR_ANDROID
317 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
318 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
319#endif
320 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000321}
djsollen@google.come63793a2012-03-21 15:39:03 +0000322
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000323#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000324const SkPath* SkPath::getSourcePath() const {
325 return fSourcePath;
326}
327
328void SkPath::setSourcePath(const SkPath* path) {
329 fSourcePath = path;
330}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000331#endif
332
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333void SkPath::reset() {
334 SkDEBUGCODE(this->validate();)
335
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000336 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000337 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338}
339
340void SkPath::rewind() {
341 SkDEBUGCODE(this->validate();)
342
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000343 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000344 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345}
346
reed@google.com7e6c4d12012-05-10 14:05:43 +0000347bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000348 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000349
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000350 if (2 == verbCount) {
351 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
352 if (kLine_Verb == fPathRef->atVerb(1)) {
353 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000354 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000355 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000356 line[0] = pts[0];
357 line[1] = pts[1];
358 }
359 return true;
360 }
361 }
362 return false;
363}
364
caryclark@google.comf1316942011-07-26 19:54:45 +0000365/*
366 Determines if path is a rect by keeping track of changes in direction
367 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000368
caryclark@google.comf1316942011-07-26 19:54:45 +0000369 The direction is computed such that:
370 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000371 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000372 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000373 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000374
caryclark@google.comf1316942011-07-26 19:54:45 +0000375A rectangle cycles up/right/down/left or up/left/down/right.
376
377The test fails if:
378 The path is closed, and followed by a line.
379 A second move creates a new endpoint.
380 A diagonal line is parsed.
381 There's more than four changes of direction.
382 There's a discontinuity on the line (e.g., a move in the middle)
383 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000384 The path contains a quadratic or cubic.
385 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000386 *The rectangle doesn't complete a cycle.
387 *The final point isn't equal to the first point.
388
389 *These last two conditions we relax if we have a 3-edge path that would
390 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000391
caryclark@google.comf1316942011-07-26 19:54:45 +0000392It's OK if the path has:
393 Several colinear line segments composing a rectangle side.
394 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000395
caryclark@google.comf1316942011-07-26 19:54:45 +0000396The direction takes advantage of the corners found since opposite sides
397must travel in opposite directions.
398
399FIXME: Allow colinear quads and cubics to be treated like lines.
400FIXME: If the API passes fill-only, return true if the filled stroke
401 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000402
403 first,last,next direction state-machine:
404 0x1 is set if the segment is horizontal
405 0x2 is set if the segment is moving to the right or down
406 thus:
407 two directions are opposites iff (dirA ^ dirB) == 0x2
408 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000411static int rect_make_dir(SkScalar dx, SkScalar dy) {
412 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
413}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000414bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
415 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000416 int corners = 0;
417 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000418 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000419 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000420 first.set(0, 0);
421 last.set(0, 0);
422 int firstDirection = 0;
423 int lastDirection = 0;
424 int nextDirection = 0;
425 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000426 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000427 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000428 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
429 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000430 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000431 savePts = pts;
432 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000433 autoClose = true;
434 case kLine_Verb: {
435 SkScalar left = last.fX;
436 SkScalar top = last.fY;
437 SkScalar right = pts->fX;
438 SkScalar bottom = pts->fY;
439 ++pts;
440 if (left != right && top != bottom) {
441 return false; // diagonal
442 }
443 if (left == right && top == bottom) {
444 break; // single point on side OK
445 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 if (0 == corners) {
448 firstDirection = nextDirection;
449 first = last;
450 last = pts[-1];
451 corners = 1;
452 closedOrMoved = false;
453 break;
454 }
455 if (closedOrMoved) {
456 return false; // closed followed by a line
457 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000458 if (autoClose && nextDirection == firstDirection) {
459 break; // colinear with first
460 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 closedOrMoved = autoClose;
462 if (lastDirection != nextDirection) {
463 if (++corners > 4) {
464 return false; // too many direction changes
465 }
466 }
467 last = pts[-1];
468 if (lastDirection == nextDirection) {
469 break; // colinear segment
470 }
471 // Possible values for corners are 2, 3, and 4.
472 // When corners == 3, nextDirection opposes firstDirection.
473 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000474 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
476 if ((directionCycle ^ turn) != nextDirection) {
477 return false; // direction didn't follow cycle
478 }
479 break;
480 }
481 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000482 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000483 case kCubic_Verb:
484 return false; // quadratic, cubic not allowed
485 case kMove_Verb:
486 last = *pts++;
487 closedOrMoved = true;
488 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000489 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000490 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000491 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000493 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 lastDirection = nextDirection;
495 }
496 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000497 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000498 if (!result) {
499 // check if we are just an incomplete rectangle, in which case we can
500 // return true, but not claim to be closed.
501 // e.g.
502 // 3 sided rectangle
503 // 4 sided but the last edge is not long enough to reach the start
504 //
505 SkScalar closeX = first.x() - last.x();
506 SkScalar closeY = first.y() - last.y();
507 if (closeX && closeY) {
508 return false; // we're diagonal, abort (can we ever reach this?)
509 }
510 int closeDirection = rect_make_dir(closeX, closeY);
511 // make sure the close-segment doesn't double-back on itself
512 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
513 result = true;
514 autoClose = false; // we are not closed
515 }
516 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000517 if (savePts) {
518 *ptsPtr = savePts;
519 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000520 if (result && isClosed) {
521 *isClosed = autoClose;
522 }
523 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000524 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000525 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 return result;
527}
528
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000529SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
530 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000531 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
532 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000533 bool isClosed = false;
534 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
535}
536
robertphillips4f662e62014-12-29 14:06:51 -0800537bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000538 SkDEBUGCODE(this->validate();)
539 int currVerb = 0;
540 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800541 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800542 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800543 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000544 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800545 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800546 int32_t num = SkToS32(pts - first);
547 if (num) {
548 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800549 } else {
550 // 'pts' isn't updated for open rects
551 *rect = this->getBounds();
552 }
553 }
554 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000555}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000556
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000557bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 SkDEBUGCODE(this->validate();)
559 int currVerb = 0;
560 const SkPoint* pts = fPathRef->points();
561 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000562 Direction testDirs[2];
563 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000564 return false;
565 }
566 const SkPoint* last = pts;
567 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000568 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000569 testRects[0].set(first, SkToS32(last - first));
570 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000571 if (testRects[0].contains(testRects[1])) {
572 if (rects) {
573 rects[0] = testRects[0];
574 rects[1] = testRects[1];
575 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000576 if (dirs) {
577 dirs[0] = testDirs[0];
578 dirs[1] = testDirs[1];
579 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000580 return true;
581 }
582 if (testRects[1].contains(testRects[0])) {
583 if (rects) {
584 rects[0] = testRects[1];
585 rects[1] = testRects[0];
586 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000587 if (dirs) {
588 dirs[0] = testDirs[1];
589 dirs[1] = testDirs[0];
590 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 return true;
592 }
593 }
594 return false;
595}
596
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000597int SkPath::countPoints() const {
598 return fPathRef->countPoints();
599}
600
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000601int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000602 SkDEBUGCODE(this->validate();)
603
604 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000605 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000606 int count = SkMin32(max, fPathRef->countPoints());
607 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
608 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609}
610
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000611SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000612 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
613 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000614 }
615 return SkPoint::Make(0, 0);
616}
617
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000618int SkPath::countVerbs() const {
619 return fPathRef->countVerbs();
620}
621
622static inline void copy_verbs_reverse(uint8_t* inorderDst,
623 const uint8_t* reversedSrc,
624 int count) {
625 for (int i = 0; i < count; ++i) {
626 inorderDst[i] = reversedSrc[~i];
627 }
628}
629
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000630int SkPath::getVerbs(uint8_t dst[], int max) const {
631 SkDEBUGCODE(this->validate();)
632
633 SkASSERT(max >= 0);
634 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 int count = SkMin32(max, fPathRef->countVerbs());
636 copy_verbs_reverse(dst, fPathRef->verbs(), count);
637 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000638}
639
reed@google.com294dd7b2011-10-11 11:58:32 +0000640bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 SkDEBUGCODE(this->validate();)
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000644 if (count > 0) {
645 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000648 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000650 if (lastPt) {
651 lastPt->set(0, 0);
652 }
653 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654}
655
656void SkPath::setLastPt(SkScalar x, SkScalar y) {
657 SkDEBUGCODE(this->validate();)
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 if (count == 0) {
661 this->moveTo(x, y);
662 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor ed(&fPathRef);
664 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
666}
667
reed@google.com04863fa2011-05-15 04:08:24 +0000668void SkPath::setConvexity(Convexity c) {
669 if (fConvexity != c) {
670 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000671 }
672}
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674//////////////////////////////////////////////////////////////////////////////
675// Construction methods
676
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000677#define DIRTY_AFTER_EDIT \
678 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000679 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000680 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000681 } while (0)
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683void SkPath::incReserve(U16CPU inc) {
684 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687}
688
689void SkPath::moveTo(SkScalar x, SkScalar y) {
690 SkDEBUGCODE(this->validate();)
691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000694 // remember our index
695 fLastMoveToIndex = fPathRef->countPoints();
696
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700698
699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rMoveTo(SkScalar x, SkScalar y) {
703 SkPoint pt;
704 this->getLastPt(&pt);
705 this->moveTo(pt.fX + x, pt.fY + y);
706}
707
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000708void SkPath::injectMoveToIfNeeded() {
709 if (fLastMoveToIndex < 0) {
710 SkScalar x, y;
711 if (fPathRef->countVerbs() == 0) {
712 x = y = 0;
713 } else {
714 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
715 x = pt.fX;
716 y = pt.fY;
717 }
718 this->moveTo(x, y);
719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722void SkPath::lineTo(SkScalar x, SkScalar y) {
723 SkDEBUGCODE(this->validate();)
724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 this->injectMoveToIfNeeded();
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 SkPathRef::Editor ed(&fPathRef);
728 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729
reed@google.comb54455e2011-05-16 14:16:04 +0000730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000734 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->lineTo(pt.fX + x, pt.fY + y);
738}
739
740void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000743 this->injectMoveToIfNeeded();
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000749
reed@google.comb54455e2011-05-16 14:16:04 +0000750 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751}
752
753void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000754 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755 SkPoint pt;
756 this->getLastPt(&pt);
757 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
758}
759
reed@google.com277c3f82013-05-31 15:17:50 +0000760void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
761 SkScalar w) {
762 // check for <= 0 or NaN with this test
763 if (!(w > 0)) {
764 this->lineTo(x2, y2);
765 } else if (!SkScalarIsFinite(w)) {
766 this->lineTo(x1, y1);
767 this->lineTo(x2, y2);
768 } else if (SK_Scalar1 == w) {
769 this->quadTo(x1, y1, x2, y2);
770 } else {
771 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
reed@google.com277c3f82013-05-31 15:17:50 +0000775 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000776 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000777 pts[0].set(x1, y1);
778 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000779
reed@google.com277c3f82013-05-31 15:17:50 +0000780 DIRTY_AFTER_EDIT;
781 }
782}
783
784void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
785 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
790}
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar x3, SkScalar y3) {
794 SkDEBUGCODE(this->validate();)
795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
802 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803
reed@google.comb54455e2011-05-16 14:16:04 +0000804 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805}
806
807void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
808 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
813 pt.fX + x3, pt.fY + y3);
814}
815
816void SkPath::close() {
817 SkDEBUGCODE(this->validate();)
818
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000819 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 case kLine_Verb:
823 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000824 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 case kMove_Verb: {
827 SkPathRef::Editor ed(&fPathRef);
828 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 }
reed@google.com277c3f82013-05-31 15:17:50 +0000831 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000832 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000833 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000834 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000835 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000836 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 }
838 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839
840 // signal that we need a moveTo to follow us (unless we're done)
841#if 0
842 if (fLastMoveToIndex >= 0) {
843 fLastMoveToIndex = ~fLastMoveToIndex;
844 }
845#else
846 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
847#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000851
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000852static void assert_known_direction(int dir) {
853 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
854}
855
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856void SkPath::addRect(const SkRect& rect, Direction dir) {
857 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
858}
859
860void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
861 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000862 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000863 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
864 SkAutoDisableDirectionCheck addc(this);
865
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
867
868 this->incReserve(5);
869
870 this->moveTo(left, top);
871 if (dir == kCCW_Direction) {
872 this->lineTo(left, bottom);
873 this->lineTo(right, bottom);
874 this->lineTo(right, top);
875 } else {
876 this->lineTo(right, top);
877 this->lineTo(right, bottom);
878 this->lineTo(left, bottom);
879 }
880 this->close();
881}
882
reed@google.com744faba2012-05-29 19:54:52 +0000883void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
884 SkDEBUGCODE(this->validate();)
885 if (count <= 0) {
886 return;
887 }
888
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000889 fLastMoveToIndex = fPathRef->countPoints();
890
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000891 // +close makes room for the extra kClose_Verb
892 SkPathRef::Editor ed(&fPathRef, count+close, count);
893
894 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000895 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000896 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
897 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000898 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000899
reed@google.com744faba2012-05-29 19:54:52 +0000900 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000901 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000902 }
903
reed@google.com744faba2012-05-29 19:54:52 +0000904 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000905 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000906}
907
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000908#include "SkGeometry.h"
909
910static int build_arc_points(const SkRect& oval, SkScalar startAngle,
911 SkScalar sweepAngle,
912 SkPoint pts[kSkBuildQuadArcStorage]) {
913
914 if (0 == sweepAngle &&
915 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
916 // Chrome uses this path to move into and out of ovals. If not
917 // treated as a special case the moves can distort the oval's
918 // bounding box (and break the circle special case).
919 pts[0].set(oval.fRight, oval.centerY());
920 return 1;
921 } else if (0 == oval.width() && 0 == oval.height()) {
922 // Chrome will sometimes create 0 radius round rects. Having degenerate
923 // quad segments in the path prevents the path from being recognized as
924 // a rect.
925 // TODO: optimizing the case where only one of width or height is zero
926 // should also be considered. This case, however, doesn't seem to be
927 // as common as the single point case.
928 pts[0].set(oval.fRight, oval.fTop);
929 return 1;
930 }
931
932 SkVector start, stop;
933
934 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
935 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
936 &stop.fX);
937
938 /* If the sweep angle is nearly (but less than) 360, then due to precision
939 loss in radians-conversion and/or sin/cos, we may end up with coincident
940 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
941 of drawing a nearly complete circle (good).
942 e.g. canvas.drawArc(0, 359.99, ...)
943 -vs- canvas.drawArc(0, 359.9, ...)
944 We try to detect this edge case, and tweak the stop vector
945 */
946 if (start == stop) {
947 SkScalar sw = SkScalarAbs(sweepAngle);
948 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
949 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
950 // make a guess at a tiny angle (in radians) to tweak by
951 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
952 // not sure how much will be enough, so we use a loop
953 do {
954 stopRad -= deltaRad;
955 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
956 } while (start == stop);
957 }
958 }
959
960 SkMatrix matrix;
961
962 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
963 matrix.postTranslate(oval.centerX(), oval.centerY());
964
965 return SkBuildQuadArc(start, stop,
966 sweepAngle > 0 ? kCW_SkRotationDirection :
967 kCCW_SkRotationDirection,
968 &matrix, pts);
969}
970
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000971void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000973 SkRRect rrect;
974 rrect.setRectRadii(rect, (const SkVector*) radii);
975 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976}
977
reed1b28a3a2014-12-17 14:42:09 -0800978#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000979/* The inline clockwise and counterclockwise round rect quad approximations
980 make it easier to see the symmetry patterns used by add corner quads.
981Clockwise corner value
982 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
983 path->quadTo(rect.fLeft, rect.fTop + offPtY,
984 rect.fLeft + midPtX, rect.fTop + midPtY);
985 path->quadTo(rect.fLeft + offPtX, rect.fTop,
986 rect.fLeft + rx, rect.fTop);
987
988 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
989 path->quadTo(rect.fRight - offPtX, rect.fTop,
990 rect.fRight - midPtX, rect.fTop + midPtY);
991 path->quadTo(rect.fRight, rect.fTop + offPtY,
992 rect.fRight, rect.fTop + ry);
993
994 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
995 path->quadTo(rect.fRight, rect.fBottom - offPtY,
996 rect.fRight - midPtX, rect.fBottom - midPtY);
997 path->quadTo(rect.fRight - offPtX, rect.fBottom,
998 rect.fRight - rx, rect.fBottom);
999
1000 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
1001 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
1002 rect.fLeft + midPtX, rect.fBottom - midPtY);
1003 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
1004 rect.fLeft, rect.fBottom - ry);
1005
1006Counterclockwise
1007 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
1008 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
1009 rect.fLeft + midPtX, rect.fBottom - midPtY);
1010 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
1011 rect.fLeft + rx, rect.fBottom);
1012
1013 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
1014 path->quadTo(rect.fRight - offPtX, rect.fBottom,
1015 rect.fRight - midPtX, rect.fBottom - midPtY);
1016 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1017 rect.fRight, rect.fBottom - ry);
1018
1019 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1020 path->quadTo(rect.fRight, rect.fTop + offPtY,
1021 rect.fRight - midPtX, rect.fTop + midPtY);
1022 path->quadTo(rect.fRight - offPtX, rect.fTop,
1023 rect.fRight - rx, rect.fTop);
1024
1025 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1026 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1027 rect.fLeft + midPtX, rect.fTop + midPtY);
1028 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1029 rect.fLeft, rect.fTop + ry);
1030*/
1031static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1032 SkRRect::Corner corner, SkPath::Direction dir) {
1033 const SkRect& rect = rrect.rect();
1034 const SkVector& radii = rrect.radii(corner);
1035 SkScalar rx = radii.fX;
1036 SkScalar ry = radii.fY;
1037 // The mid point of the quadratic arc approximation is half way between the two
1038 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001039 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1040 SkScalar midPtX = rx * mid;
1041 SkScalar midPtY = ry * mid;
1042 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1043 SkScalar offPtX = rx * control;
1044 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001045 static const int kCornerPts = 5;
1046 SkScalar xOff[kCornerPts];
1047 SkScalar yOff[kCornerPts];
1048
1049 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1050 SkASSERT(dir == SkPath::kCCW_Direction
1051 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1052 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1053 xOff[0] = xOff[1] = 0;
1054 xOff[2] = midPtX;
1055 xOff[3] = offPtX;
1056 xOff[4] = rx;
1057 yOff[0] = ry;
1058 yOff[1] = offPtY;
1059 yOff[2] = midPtY;
1060 yOff[3] = yOff[4] = 0;
1061 } else {
1062 xOff[0] = rx;
1063 xOff[1] = offPtX;
1064 xOff[2] = midPtX;
1065 xOff[3] = xOff[4] = 0;
1066 yOff[0] = yOff[1] = 0;
1067 yOff[2] = midPtY;
1068 yOff[3] = offPtY;
1069 yOff[4] = ry;
1070 }
1071 if ((corner - 1) & 2) {
1072 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1073 for (int i = 0; i < kCornerPts; ++i) {
1074 xOff[i] = rect.fLeft + xOff[i];
1075 }
1076 } else {
1077 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1078 for (int i = 0; i < kCornerPts; ++i) {
1079 xOff[i] = rect.fRight - xOff[i];
1080 }
1081 }
1082 if (corner < SkRRect::kLowerRight_Corner) {
1083 for (int i = 0; i < kCornerPts; ++i) {
1084 yOff[i] = rect.fTop + yOff[i];
1085 }
1086 } else {
1087 for (int i = 0; i < kCornerPts; ++i) {
1088 yOff[i] = rect.fBottom - yOff[i];
1089 }
1090 }
1091
1092 SkPoint lastPt;
1093 SkAssertResult(path->getLastPt(&lastPt));
1094 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1095 path->lineTo(xOff[0], yOff[0]);
1096 }
1097 if (rx || ry) {
1098 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1099 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1100 } else {
1101 path->lineTo(xOff[2], yOff[2]);
1102 path->lineTo(xOff[4], yOff[4]);
1103 }
1104}
reed1b28a3a2014-12-17 14:42:09 -08001105#endif
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001106
reed@google.com4ed0fb72012-12-12 20:48:18 +00001107void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108 assert_known_direction(dir);
1109
1110 if (rrect.isEmpty()) {
1111 return;
1112 }
1113
reed@google.com4ed0fb72012-12-12 20:48:18 +00001114 const SkRect& bounds = rrect.getBounds();
1115
1116 if (rrect.isRect()) {
1117 this->addRect(bounds, dir);
1118 } else if (rrect.isOval()) {
1119 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001120 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001121 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001123 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001124 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001125
reed1b28a3a2014-12-17 14:42:09 -08001126#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001127 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001128 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001129 this->moveTo(bounds.fLeft,
1130 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1131 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1132 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1133 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1134 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001135 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001136 this->moveTo(bounds.fLeft,
1137 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1138 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1139 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1140 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1141 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001142 }
reed1b28a3a2014-12-17 14:42:09 -08001143#else
1144 const SkScalar L = bounds.fLeft;
1145 const SkScalar T = bounds.fTop;
1146 const SkScalar R = bounds.fRight;
1147 const SkScalar B = bounds.fBottom;
1148 const SkScalar W = SK_ScalarRoot2Over2;
1149
1150 this->incReserve(13);
1151 if (kCW_Direction == dir) {
1152 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1153
1154 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1155 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1156
1157 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1158 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1159
1160 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1161 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1162
1163 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1164 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1165 } else {
1166 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1167
1168 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1169 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1170
1171 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1172 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1173
1174 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1175 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1176
1177 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1178 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1179 }
1180#endif
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001181 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001182 }
reed5bcbe912014-12-15 12:28:33 -08001183 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001184}
1185
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001186bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001187 int count = fPathRef->countVerbs();
1188 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1189 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190 if (*verbs == kLine_Verb ||
1191 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001192 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193 *verbs == kCubic_Verb) {
1194 return false;
1195 }
1196 ++verbs;
1197 }
1198 return true;
1199}
1200
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001201void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1202 Direction dir) {
1203 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001204
humper@google.com75e3ca12013-04-08 21:44:11 +00001205 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001206 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001207 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001208 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001209 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1210 return;
1211 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001212
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001213 SkRRect rrect;
1214 rrect.setRectXY(rect, rx, ry);
1215 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001216}
1217
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001219 assert_known_direction(dir);
1220
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001221 /* If addOval() is called after previous moveTo(),
1222 this path is still marked as an oval. This is used to
1223 fit into WebKit's calling sequences.
1224 We can't simply check isEmpty() in this case, as additional
1225 moveTo() would mark the path non empty.
1226 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001227 bool isOval = hasOnlyMoveTos();
1228 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001229 fDirection = dir;
1230 } else {
1231 fDirection = kUnknown_Direction;
1232 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001233
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001234 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 SkAutoPathBoundsUpdate apbu(this, oval);
1237
reed220f9262014-12-17 08:21:04 -08001238#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239 SkScalar cx = oval.centerX();
1240 SkScalar cy = oval.centerY();
1241 SkScalar rx = SkScalarHalf(oval.width());
1242 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1245 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1246 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1247 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1248
1249 /*
reed220f9262014-12-17 08:21:04 -08001250 To handle imprecision in computing the center and radii, we revert to
1251 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1252 to ensure that we don't exceed the oval's bounds *ever*, since we want
1253 to use oval for our fast-bounds, rather than have to recompute it.
1254 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 const SkScalar L = oval.fLeft; // cx - rx
1256 const SkScalar T = oval.fTop; // cy - ry
1257 const SkScalar R = oval.fRight; // cx + rx
1258 const SkScalar B = oval.fBottom; // cy + ry
1259
1260 this->incReserve(17); // 8 quads + close
1261 this->moveTo(R, cy);
1262 if (dir == kCCW_Direction) {
1263 this->quadTo( R, cy - sy, cx + mx, cy - my);
1264 this->quadTo(cx + sx, T, cx , T);
1265 this->quadTo(cx - sx, T, cx - mx, cy - my);
1266 this->quadTo( L, cy - sy, L, cy );
1267 this->quadTo( L, cy + sy, cx - mx, cy + my);
1268 this->quadTo(cx - sx, B, cx , B);
1269 this->quadTo(cx + sx, B, cx + mx, cy + my);
1270 this->quadTo( R, cy + sy, R, cy );
1271 } else {
1272 this->quadTo( R, cy + sy, cx + mx, cy + my);
1273 this->quadTo(cx + sx, B, cx , B);
1274 this->quadTo(cx - sx, B, cx - mx, cy + my);
1275 this->quadTo( L, cy + sy, L, cy );
1276 this->quadTo( L, cy - sy, cx - mx, cy - my);
1277 this->quadTo(cx - sx, T, cx , T);
1278 this->quadTo(cx + sx, T, cx + mx, cy - my);
1279 this->quadTo( R, cy - sy, R, cy );
1280 }
reed220f9262014-12-17 08:21:04 -08001281#else
1282 const SkScalar L = oval.fLeft;
1283 const SkScalar T = oval.fTop;
1284 const SkScalar R = oval.fRight;
1285 const SkScalar B = oval.fBottom;
1286 const SkScalar cx = oval.centerX();
1287 const SkScalar cy = oval.centerY();
1288 const SkScalar weight = SK_ScalarRoot2Over2;
1289
1290 this->incReserve(9); // move + 4 conics
1291 this->moveTo(R, cy);
1292 if (dir == kCCW_Direction) {
1293 this->conicTo(R, T, cx, T, weight);
1294 this->conicTo(L, T, L, cy, weight);
1295 this->conicTo(L, B, cx, B, weight);
1296 this->conicTo(R, B, R, cy, weight);
1297 } else {
1298 this->conicTo(R, B, cx, B, weight);
1299 this->conicTo(L, B, L, cy, weight);
1300 this->conicTo(L, T, cx, T, weight);
1301 this->conicTo(R, T, R, cy, weight);
1302 }
1303#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305
robertphillips@google.com466310d2013-12-03 16:43:54 +00001306 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001307
robertphillips@google.com466310d2013-12-03 16:43:54 +00001308 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001309}
1310
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1312 if (r > 0) {
1313 SkRect rect;
1314 rect.set(x - r, y - r, x + r, y + r);
1315 this->addOval(rect, dir);
1316 }
1317}
1318
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1320 bool forceMoveTo) {
1321 if (oval.width() < 0 || oval.height() < 0) {
1322 return;
1323 }
1324
1325 SkPoint pts[kSkBuildQuadArcStorage];
1326 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1327 SkASSERT((count & 1) == 1);
1328
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001329 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 forceMoveTo = true;
1331 }
1332 this->incReserve(count);
1333 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1334 for (int i = 1; i < count; i += 2) {
1335 this->quadTo(pts[i], pts[i+1]);
1336 }
1337}
1338
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001339void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 if (oval.isEmpty() || 0 == sweepAngle) {
1341 return;
1342 }
1343
1344 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1345
1346 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1347 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1348 return;
1349 }
1350
1351 SkPoint pts[kSkBuildQuadArcStorage];
1352 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1353
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001354 SkDEBUGCODE(this->validate();)
1355 SkASSERT(count & 1);
1356
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001357 fLastMoveToIndex = fPathRef->countPoints();
1358
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001359 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1360
1361 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1362 if (count > 1) {
1363 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1364 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001366
1367 DIRTY_AFTER_EDIT;
1368 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369}
1370
1371/*
1372 Need to handle the case when the angle is sharp, and our computed end-points
1373 for the arc go behind pt1 and/or p2...
1374*/
1375void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1376 SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001377 if (radius == 0) {
1378 this->lineTo(x1, y1);
1379 return;
1380 }
1381
1382 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001383
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 // need to know our prev pt so we can construct tangent vectors
1385 {
1386 SkPoint start;
1387 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001388 // Handle degenerate cases by adding a line to the first point and
1389 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390 before.setNormalize(x1 - start.fX, y1 - start.fY);
1391 after.setNormalize(x2 - x1, y2 - y1);
1392 }
reed@google.comabf15c12011-01-18 20:35:51 +00001393
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 SkScalar cosh = SkPoint::DotProduct(before, after);
1395 SkScalar sinh = SkPoint::CrossProduct(before, after);
1396
1397 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001398 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 return;
1400 }
reed@google.comabf15c12011-01-18 20:35:51 +00001401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1403 if (dist < 0) {
1404 dist = -dist;
1405 }
1406
1407 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1408 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1409 SkRotationDirection arcDir;
1410
1411 // now turn before/after into normals
1412 if (sinh > 0) {
1413 before.rotateCCW();
1414 after.rotateCCW();
1415 arcDir = kCW_SkRotationDirection;
1416 } else {
1417 before.rotateCW();
1418 after.rotateCW();
1419 arcDir = kCCW_SkRotationDirection;
1420 }
1421
1422 SkMatrix matrix;
1423 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001424
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 matrix.setScale(radius, radius);
1426 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1427 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001428
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001430
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 this->incReserve(count);
1432 // [xx,yy] == pts[0]
1433 this->lineTo(xx, yy);
1434 for (int i = 1; i < count; i += 2) {
1435 this->quadTo(pts[i], pts[i+1]);
1436 }
1437}
1438
1439///////////////////////////////////////////////////////////////////////////////
1440
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001441void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 SkMatrix matrix;
1443
1444 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001445 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446}
1447
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001448void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001449 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001451 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 SkPoint pts[4];
1453 Verb verb;
1454
1455 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001456 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 while ((verb = iter.next(pts)) != kDone_Verb) {
1458 switch (verb) {
1459 case kMove_Verb:
1460 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001461 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1462 injectMoveToIfNeeded(); // In case last contour is closed
1463 this->lineTo(pts[0]);
1464 } else {
1465 this->moveTo(pts[0]);
1466 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 break;
1468 case kLine_Verb:
1469 proc(matrix, &pts[1], &pts[1], 1);
1470 this->lineTo(pts[1]);
1471 break;
1472 case kQuad_Verb:
1473 proc(matrix, &pts[1], &pts[1], 2);
1474 this->quadTo(pts[1], pts[2]);
1475 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001476 case kConic_Verb:
1477 proc(matrix, &pts[1], &pts[1], 2);
1478 this->conicTo(pts[1], pts[2], iter.conicWeight());
1479 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 case kCubic_Verb:
1481 proc(matrix, &pts[1], &pts[1], 3);
1482 this->cubicTo(pts[1], pts[2], pts[3]);
1483 break;
1484 case kClose_Verb:
1485 this->close();
1486 break;
1487 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001488 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001490 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 }
1492}
1493
1494///////////////////////////////////////////////////////////////////////////////
1495
reed@google.com277c3f82013-05-31 15:17:50 +00001496static int pts_in_verb(unsigned verb) {
1497 static const uint8_t gPtsInVerb[] = {
1498 1, // kMove
1499 1, // kLine
1500 2, // kQuad
1501 2, // kConic
1502 3, // kCubic
1503 0, // kClose
1504 0 // kDone
1505 };
1506
1507 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1508 return gPtsInVerb[verb];
1509}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511// ignore the last point of the 1st contour
1512void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001513 int i, vcount = path.fPathRef->countVerbs();
1514 // exit early if the path is empty, or just has a moveTo.
1515 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 return;
1517 }
1518
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001521 const uint8_t* verbs = path.fPathRef->verbs();
1522 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001523 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 SkASSERT(verbs[~0] == kMove_Verb);
1526 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001527 unsigned v = verbs[~i];
1528 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 if (n == 0) {
1530 break;
1531 }
1532 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001533 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 }
1535
1536 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 case kLine_Verb:
1539 this->lineTo(pts[-1].fX, pts[-1].fY);
1540 break;
1541 case kQuad_Verb:
1542 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1543 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001544 case kConic_Verb:
1545 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1546 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 case kCubic_Verb:
1548 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1549 pts[-3].fX, pts[-3].fY);
1550 break;
1551 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001552 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 break;
1554 }
reed@google.com277c3f82013-05-31 15:17:50 +00001555 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 }
1557}
1558
reed@google.com63d73742012-01-10 15:33:12 +00001559void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001560 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001562 const SkPoint* pts = src.fPathRef->pointsEnd();
1563 // we will iterator through src's verbs backwards
1564 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1565 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001566 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001567
1568 bool needMove = true;
1569 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 while (verbs < verbsEnd) {
1571 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001572 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001573
1574 if (needMove) {
1575 --pts;
1576 this->moveTo(pts->fX, pts->fY);
1577 needMove = false;
1578 }
1579 pts -= n;
1580 switch (v) {
1581 case kMove_Verb:
1582 if (needClose) {
1583 this->close();
1584 needClose = false;
1585 }
1586 needMove = true;
1587 pts += 1; // so we see the point in "if (needMove)" above
1588 break;
1589 case kLine_Verb:
1590 this->lineTo(pts[0]);
1591 break;
1592 case kQuad_Verb:
1593 this->quadTo(pts[1], pts[0]);
1594 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001595 case kConic_Verb:
1596 this->conicTo(pts[1], pts[0], *--conicWeights);
1597 break;
reed@google.com63d73742012-01-10 15:33:12 +00001598 case kCubic_Verb:
1599 this->cubicTo(pts[2], pts[1], pts[0]);
1600 break;
1601 case kClose_Verb:
1602 needClose = true;
1603 break;
1604 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001605 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001606 }
1607 }
1608}
1609
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610///////////////////////////////////////////////////////////////////////////////
1611
1612void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1613 SkMatrix matrix;
1614
1615 matrix.setTranslate(dx, dy);
1616 this->transform(matrix, dst);
1617}
1618
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1620 int level = 2) {
1621 if (--level >= 0) {
1622 SkPoint tmp[7];
1623
1624 SkChopCubicAtHalf(pts, tmp);
1625 subdivide_cubic_to(path, &tmp[0], level);
1626 subdivide_cubic_to(path, &tmp[3], level);
1627 } else {
1628 path->cubicTo(pts[1], pts[2], pts[3]);
1629 }
1630}
1631
1632void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1633 SkDEBUGCODE(this->validate();)
1634 if (dst == NULL) {
1635 dst = (SkPath*)this;
1636 }
1637
tomhudson@google.com8d430182011-06-06 19:11:19 +00001638 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 SkPath tmp;
1640 tmp.fFillType = fFillType;
1641
1642 SkPath::Iter iter(*this, false);
1643 SkPoint pts[4];
1644 SkPath::Verb verb;
1645
reed@google.com4a3b7142012-05-16 17:16:46 +00001646 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 switch (verb) {
1648 case kMove_Verb:
1649 tmp.moveTo(pts[0]);
1650 break;
1651 case kLine_Verb:
1652 tmp.lineTo(pts[1]);
1653 break;
1654 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001655 // promote the quad to a conic
1656 tmp.conicTo(pts[1], pts[2],
1657 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001659 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001660 tmp.conicTo(pts[1], pts[2],
1661 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001662 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 case kCubic_Verb:
1664 subdivide_cubic_to(&tmp, pts);
1665 break;
1666 case kClose_Verb:
1667 tmp.close();
1668 break;
1669 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001670 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 break;
1672 }
1673 }
1674
1675 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001676 SkPathRef::Editor ed(&dst->fPathRef);
1677 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001678 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001680 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1681
reed@android.com8a1c16f2008-12-17 15:59:43 +00001682 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001684 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001685 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001687
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001688 if (kUnknown_Direction == fDirection) {
1689 dst->fDirection = kUnknown_Direction;
1690 } else {
1691 SkScalar det2x2 =
1692 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1693 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1694 if (det2x2 < 0) {
1695 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1696 } else if (det2x2 > 0) {
1697 dst->fDirection = fDirection;
1698 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001699 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001700 dst->fDirection = kUnknown_Direction;
1701 }
1702 }
1703
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 SkDEBUGCODE(dst->validate();)
1705 }
1706}
1707
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708///////////////////////////////////////////////////////////////////////////////
1709///////////////////////////////////////////////////////////////////////////////
1710
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001711enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001712 kEmptyContour_SegmentState, // The current contour is empty. We may be
1713 // starting processing or we may have just
1714 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001715 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1716 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1717 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718};
1719
1720SkPath::Iter::Iter() {
1721#ifdef SK_DEBUG
1722 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001723 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001726 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727#endif
1728 // need to init enough to make next() harmlessly return kDone_Verb
1729 fVerbs = NULL;
1730 fVerbStop = NULL;
1731 fNeedClose = false;
1732}
1733
1734SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1735 this->setPath(path, forceClose);
1736}
1737
1738void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001739 fPts = path.fPathRef->points();
1740 fVerbs = path.fPathRef->verbs();
1741 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001742 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001744 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 fForceClose = SkToU8(forceClose);
1746 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001747 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748}
1749
1750bool SkPath::Iter::isClosedContour() const {
1751 if (fVerbs == NULL || fVerbs == fVerbStop) {
1752 return false;
1753 }
1754 if (fForceClose) {
1755 return true;
1756 }
1757
1758 const uint8_t* verbs = fVerbs;
1759 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001760
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001761 if (kMove_Verb == *(verbs - 1)) {
1762 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 }
1764
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001765 while (verbs > stop) {
1766 // verbs points one beyond the current verb, decrement first.
1767 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 if (kMove_Verb == v) {
1769 break;
1770 }
1771 if (kClose_Verb == v) {
1772 return true;
1773 }
1774 }
1775 return false;
1776}
1777
1778SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001779 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001781 // A special case: if both points are NaN, SkPoint::operation== returns
1782 // false, but the iterator expects that they are treated as the same.
1783 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001784 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1785 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001786 return kClose_Verb;
1787 }
1788
reed@google.com9e25dbf2012-05-15 17:05:38 +00001789 pts[0] = fLastPt;
1790 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 fLastPt = fMoveTo;
1792 fCloseLine = true;
1793 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001794 } else {
1795 pts[0] = fMoveTo;
1796 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798}
1799
reed@google.com9e25dbf2012-05-15 17:05:38 +00001800const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001801 if (fSegmentState == kAfterMove_SegmentState) {
1802 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001803 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001804 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1807 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001808 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810}
1811
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812void SkPath::Iter::consumeDegenerateSegments() {
1813 // We need to step over anything that will not move the current draw point
1814 // forward before the next move is seen
1815 const uint8_t* lastMoveVerb = 0;
1816 const SkPoint* lastMovePt = 0;
1817 SkPoint lastPt = fLastPt;
1818 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 switch (verb) {
1821 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 // Keep a record of this most recent move
1823 lastMoveVerb = fVerbs;
1824 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001825 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 fPts++;
1828 break;
1829
1830 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001831 // A close when we are in a segment is always valid except when it
1832 // follows a move which follows a segment.
1833 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 return;
1835 }
1836 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 break;
1839
1840 case kLine_Verb:
1841 if (!IsLineDegenerate(lastPt, fPts[0])) {
1842 if (lastMoveVerb) {
1843 fVerbs = lastMoveVerb;
1844 fPts = lastMovePt;
1845 return;
1846 }
1847 return;
1848 }
1849 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 fPts++;
1852 break;
1853
reed@google.com277c3f82013-05-31 15:17:50 +00001854 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 case kQuad_Verb:
1856 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1857 if (lastMoveVerb) {
1858 fVerbs = lastMoveVerb;
1859 fPts = lastMovePt;
1860 return;
1861 }
1862 return;
1863 }
1864 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001865 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001867 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 break;
1869
1870 case kCubic_Verb:
1871 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1872 if (lastMoveVerb) {
1873 fVerbs = lastMoveVerb;
1874 fPts = lastMovePt;
1875 return;
1876 }
1877 return;
1878 }
1879 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001880 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001881 fPts += 3;
1882 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001883
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001885 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 }
1887 }
1888}
1889
reed@google.com4a3b7142012-05-16 17:16:46 +00001890SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001891 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 // Close the curve if requested and if there is some curve to close
1895 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001896 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 return kLine_Verb;
1898 }
1899 fNeedClose = false;
1900 return kClose_Verb;
1901 }
1902 return kDone_Verb;
1903 }
1904
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001905 // fVerbs is one beyond the current verb, decrement first
1906 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 const SkPoint* SK_RESTRICT srcPts = fPts;
1908 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909
1910 switch (verb) {
1911 case kMove_Verb:
1912 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001913 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 verb = this->autoClose(pts);
1915 if (verb == kClose_Verb) {
1916 fNeedClose = false;
1917 }
1918 return (Verb)verb;
1919 }
1920 if (fVerbs == fVerbStop) { // might be a trailing moveto
1921 return kDone_Verb;
1922 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001923 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001926 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001928 fNeedClose = fForceClose;
1929 break;
1930 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 pts[0] = this->cons_moveTo();
1932 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 fLastPt = srcPts[0];
1934 fCloseLine = false;
1935 srcPts += 1;
1936 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001937 case kConic_Verb:
1938 fConicWeights += 1;
1939 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001941 pts[0] = this->cons_moveTo();
1942 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943 fLastPt = srcPts[1];
1944 srcPts += 2;
1945 break;
1946 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001947 pts[0] = this->cons_moveTo();
1948 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 fLastPt = srcPts[2];
1950 srcPts += 3;
1951 break;
1952 case kClose_Verb:
1953 verb = this->autoClose(pts);
1954 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 } else {
1957 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001958 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 break;
1962 }
1963 fPts = srcPts;
1964 return (Verb)verb;
1965}
1966
1967///////////////////////////////////////////////////////////////////////////////
1968
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001969SkPath::RawIter::RawIter() {
1970#ifdef SK_DEBUG
1971 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001972 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001973 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1974#endif
1975 // need to init enough to make next() harmlessly return kDone_Verb
1976 fVerbs = NULL;
1977 fVerbStop = NULL;
1978}
1979
1980SkPath::RawIter::RawIter(const SkPath& path) {
1981 this->setPath(path);
1982}
1983
1984void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001985 fPts = path.fPathRef->points();
1986 fVerbs = path.fPathRef->verbs();
1987 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001988 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001989 fMoveTo.fX = fMoveTo.fY = 0;
1990 fLastPt.fX = fLastPt.fY = 0;
1991}
1992
1993SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001994 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001995 if (fVerbs == fVerbStop) {
1996 return kDone_Verb;
1997 }
1998
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001999 // fVerbs points one beyond next verb so decrement first.
2000 unsigned verb = *(--fVerbs);
2001 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002002
2003 switch (verb) {
2004 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002005 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006 fMoveTo = srcPts[0];
2007 fLastPt = fMoveTo;
2008 srcPts += 1;
2009 break;
2010 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002011 pts[0] = fLastPt;
2012 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002013 fLastPt = srcPts[0];
2014 srcPts += 1;
2015 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002016 case kConic_Verb:
2017 fConicWeights += 1;
2018 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002020 pts[0] = fLastPt;
2021 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002022 fLastPt = srcPts[1];
2023 srcPts += 2;
2024 break;
2025 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002026 pts[0] = fLastPt;
2027 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002028 fLastPt = srcPts[2];
2029 srcPts += 3;
2030 break;
2031 case kClose_Verb:
2032 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002033 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002034 break;
2035 }
2036 fPts = srcPts;
2037 return (Verb)verb;
2038}
2039
2040///////////////////////////////////////////////////////////////////////////////
2041
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044*/
2045
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002046size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 SkDEBUGCODE(this->validate();)
2048
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002049 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002050 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002051 return SkAlign4(byteCount);
2052 }
2053
2054 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002055
robertphillips@google.com466310d2013-12-03 16:43:54 +00002056 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002057 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002058 (fDirection << kDirection_SerializationShift) |
2059 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002060
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002061 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002062
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002063 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002064
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002065 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002066 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067}
2068
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002069size_t SkPath::readFromMemory(const void* storage, size_t length) {
2070 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002071
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002072 int32_t packed;
2073 if (!buffer.readS32(&packed)) {
2074 return 0;
2075 }
2076
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2078 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002079 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002080 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002081 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002082
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002083 size_t sizeRead = 0;
2084 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002085 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002086 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002087 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002088 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002089 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002090 // If the buffer is not valid, pathRef should be NULL
2091 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002092 }
2093 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094}
2095
2096///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097
reede05fed02014-12-15 07:59:53 -08002098#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002099#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002100
reed@google.com51bbe752013-01-17 22:07:50 +00002101static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002102 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002103 str->append(label);
2104 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002105
reed@google.com51bbe752013-01-17 22:07:50 +00002106 const SkScalar* values = &pts[0].fX;
2107 count *= 2;
2108
2109 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002111 if (i < count - 1) {
2112 str->append(", ");
2113 }
2114 }
reed@google.com277c3f82013-05-31 15:17:50 +00002115 if (conicWeight >= 0) {
2116 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002117 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002118 }
caryclark08fa28c2014-10-23 13:08:56 -07002119 str->append(");");
reede05fed02014-12-15 07:59:53 -08002120 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002121 str->append(" // ");
2122 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002123 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002124 if (i < count - 1) {
2125 str->append(", ");
2126 }
2127 }
2128 if (conicWeight >= 0) {
2129 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002130 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002131 }
2132 }
2133 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002134}
2135
caryclarke9562592014-09-15 09:26:09 -07002136void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002137 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 Iter iter(*this, forceClose);
2139 SkPoint pts[4];
2140 Verb verb;
2141
caryclark66a5d8b2014-06-24 08:30:15 -07002142 if (!wStream) {
2143 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2144 }
reed@google.com51bbe752013-01-17 22:07:50 +00002145 SkString builder;
2146
reed@google.com4a3b7142012-05-16 17:16:46 +00002147 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 switch (verb) {
2149 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002150 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002153 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
2155 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002156 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002158 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002159 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002160 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002162 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002165 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 default:
2168 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2169 verb = kDone_Verb; // stop the loop
2170 break;
2171 }
2172 }
caryclark66a5d8b2014-06-24 08:30:15 -07002173 if (wStream) {
2174 wStream->writeText(builder.c_str());
2175 } else {
2176 SkDebugf("%s", builder.c_str());
2177 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002178}
2179
reed@android.come522ca52009-11-23 20:10:41 +00002180void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002181 this->dump(NULL, false, false);
2182}
2183
2184void SkPath::dumpHex() const {
2185 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002186}
2187
2188#ifdef SK_DEBUG
2189void SkPath::validate() const {
2190 SkASSERT(this != NULL);
2191 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002192
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194 if (!fBoundsIsDirty) {
2195 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002198 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002199
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002200 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002201 // if we're empty, fBounds may be empty but translated, so we can't
2202 // necessarily compare to bounds directly
2203 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2204 // be [2, 2, 2, 2]
2205 SkASSERT(bounds.isEmpty());
2206 SkASSERT(fBounds.isEmpty());
2207 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 if (bounds.isEmpty()) {
2209 SkASSERT(fBounds.isEmpty());
2210 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002211 if (!fBounds.isEmpty()) {
2212 SkASSERT(fBounds.contains(bounds));
2213 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002214 }
reed@android.come522ca52009-11-23 20:10:41 +00002215 }
2216 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002217#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002218}
djsollen@google.com077348c2012-10-22 20:23:32 +00002219#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002220
reed@google.com04863fa2011-05-15 04:08:24 +00002221///////////////////////////////////////////////////////////////////////////////
2222
reed@google.com0b7b9822011-05-16 12:29:27 +00002223static int sign(SkScalar x) { return x < 0; }
2224#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002225
robertphillipsc506e302014-09-16 09:43:31 -07002226enum DirChange {
2227 kLeft_DirChange,
2228 kRight_DirChange,
2229 kStraight_DirChange,
2230 kBackwards_DirChange,
2231
2232 kInvalid_DirChange
2233};
2234
2235
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002236static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 // The error epsilon was empirically derived; worse case round rects
2238 // with a mid point outset by 2x float epsilon in tests had an error
2239 // of 12.
2240 const int epsilon = 16;
2241 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2242 return false;
2243 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002244 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002245 int aBits = SkFloatAs2sCompliment(compA);
2246 int bBits = SkFloatAs2sCompliment(compB);
2247 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002248}
2249
robertphillipsc506e302014-09-16 09:43:31 -07002250static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2251 const SkVector& lastVec, const SkVector& curVec) {
2252 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2253
2254 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2255 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2256 largest = SkTMax(largest, -smallest);
2257
2258 if (!almost_equal(largest, largest + cross)) {
2259 int sign = SkScalarSignAsInt(cross);
2260 if (sign) {
2261 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2262 }
2263 }
2264
2265 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2266 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2267 lastVec.dot(curVec) < 0.0f) {
2268 return kBackwards_DirChange;
2269 }
2270
2271 return kStraight_DirChange;
2272}
2273
reed@google.com04863fa2011-05-15 04:08:24 +00002274// only valid for a single contour
2275struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002276 Convexicator()
2277 : fPtCount(0)
2278 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002279 , fDirection(SkPath::kUnknown_Direction)
2280 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002281 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002282 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002283 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002284 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002285 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002286 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002287
2288 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002289 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002290 }
2291
2292 SkPath::Convexity getConvexity() const { return fConvexity; }
2293
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002294 /** The direction returned is only valid if the path is determined convex */
2295 SkPath::Direction getDirection() const { return fDirection; }
2296
reed@google.com04863fa2011-05-15 04:08:24 +00002297 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002298 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002299 return;
2300 }
2301
2302 if (0 == fPtCount) {
2303 fCurrPt = pt;
2304 ++fPtCount;
2305 } else {
2306 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002307 SkScalar lengthSqd = vec.lengthSqd();
2308 if (!SkScalarIsFinite(lengthSqd)) {
2309 fIsFinite = false;
2310 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002311 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 fCurrPt = pt;
2313 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002314 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002315 } else {
2316 SkASSERT(fPtCount > 2);
2317 this->addVec(vec);
2318 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002319
reed@google.com85b6e392011-05-15 20:25:17 +00002320 int sx = sign(vec.fX);
2321 int sy = sign(vec.fY);
2322 fDx += (sx != fSx);
2323 fDy += (sy != fSy);
2324 fSx = sx;
2325 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002326
reed@google.com85b6e392011-05-15 20:25:17 +00002327 if (fDx > 3 || fDy > 3) {
2328 fConvexity = SkPath::kConcave_Convexity;
2329 }
reed@google.com04863fa2011-05-15 04:08:24 +00002330 }
2331 }
2332 }
2333
2334 void close() {
2335 if (fPtCount > 2) {
2336 this->addVec(fFirstVec);
2337 }
2338 }
2339
caryclarkd3d1a982014-12-08 04:57:38 -08002340 bool isFinite() const {
2341 return fIsFinite;
2342 }
2343
reed@google.com04863fa2011-05-15 04:08:24 +00002344private:
2345 void addVec(const SkVector& vec) {
2346 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002347 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2348 switch (dir) {
2349 case kLeft_DirChange: // fall through
2350 case kRight_DirChange:
2351 if (kInvalid_DirChange == fExpectedDir) {
2352 fExpectedDir = dir;
2353 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2354 : SkPath::kCCW_Direction;
2355 } else if (dir != fExpectedDir) {
2356 fConvexity = SkPath::kConcave_Convexity;
2357 fDirection = SkPath::kUnknown_Direction;
2358 }
2359 fLastVec = vec;
2360 break;
2361 case kStraight_DirChange:
2362 break;
2363 case kBackwards_DirChange:
2364 fLastVec = vec;
2365 break;
2366 case kInvalid_DirChange:
2367 SkFAIL("Use of invalid direction change flag");
2368 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002369 }
2370 }
2371
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002372 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002373 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002374 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2375 // value with the current vec is deemed to be of a significant value.
2376 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002377 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002378 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002379 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002380 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002381 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002382 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002383};
2384
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385SkPath::Convexity SkPath::internalGetConvexity() const {
2386 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002387 SkPoint pts[4];
2388 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002390
2391 int contourCount = 0;
2392 int count;
2393 Convexicator state;
2394
caryclarkd3d1a982014-12-08 04:57:38 -08002395 if (!isFinite()) {
2396 return kUnknown_Convexity;
2397 }
reed@google.com04863fa2011-05-15 04:08:24 +00002398 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2399 switch (verb) {
2400 case kMove_Verb:
2401 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002402 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002403 return kConcave_Convexity;
2404 }
2405 pts[1] = pts[0];
2406 count = 1;
2407 break;
2408 case kLine_Verb: count = 1; break;
2409 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002410 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002411 case kCubic_Verb: count = 3; break;
2412 case kClose_Verb:
2413 state.close();
2414 count = 0;
2415 break;
2416 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002417 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002418 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 return kConcave_Convexity;
2420 }
2421
2422 for (int i = 1; i <= count; i++) {
2423 state.addPt(pts[i]);
2424 }
2425 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002426 if (!state.isFinite()) {
2427 return kUnknown_Convexity;
2428 }
reed@google.com04863fa2011-05-15 04:08:24 +00002429 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002430 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002431 return kConcave_Convexity;
2432 }
2433 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002434 fConvexity = state.getConvexity();
2435 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2436 fDirection = state.getDirection();
2437 }
2438 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002439}
reed@google.com69a99432012-01-10 18:00:10 +00002440
2441///////////////////////////////////////////////////////////////////////////////
2442
2443class ContourIter {
2444public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002445 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002446
2447 bool done() const { return fDone; }
2448 // if !done() then these may be called
2449 int count() const { return fCurrPtCount; }
2450 const SkPoint* pts() const { return fCurrPt; }
2451 void next();
2452
2453private:
2454 int fCurrPtCount;
2455 const SkPoint* fCurrPt;
2456 const uint8_t* fCurrVerb;
2457 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002458 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002459 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002460 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002461};
2462
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002463ContourIter::ContourIter(const SkPathRef& pathRef) {
2464 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002465 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002466 fCurrPt = pathRef.points();
2467 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002468 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002469 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002470 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002471 this->next();
2472}
2473
2474void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002475 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002476 fDone = true;
2477 }
2478 if (fDone) {
2479 return;
2480 }
2481
2482 // skip pts of prev contour
2483 fCurrPt += fCurrPtCount;
2484
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002485 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002486 int ptCount = 1; // moveTo
2487 const uint8_t* verbs = fCurrVerb;
2488
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002489 for (--verbs; verbs > fStopVerbs; --verbs) {
2490 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002491 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002492 goto CONTOUR_END;
2493 case SkPath::kLine_Verb:
2494 ptCount += 1;
2495 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002496 case SkPath::kConic_Verb:
2497 fCurrConicWeight += 1;
2498 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002499 case SkPath::kQuad_Verb:
2500 ptCount += 2;
2501 break;
2502 case SkPath::kCubic_Verb:
2503 ptCount += 3;
2504 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002505 case SkPath::kClose_Verb:
2506 break;
2507 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002508 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002509 break;
2510 }
2511 }
2512CONTOUR_END:
2513 fCurrPtCount = ptCount;
2514 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002515 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002516}
2517
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002518// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002519static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002520 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2521 // We may get 0 when the above subtracts underflow. We expect this to be
2522 // very rare and lazily promote to double.
2523 if (0 == cross) {
2524 double p0x = SkScalarToDouble(p0.fX);
2525 double p0y = SkScalarToDouble(p0.fY);
2526
2527 double p1x = SkScalarToDouble(p1.fX);
2528 double p1y = SkScalarToDouble(p1.fY);
2529
2530 double p2x = SkScalarToDouble(p2.fX);
2531 double p2y = SkScalarToDouble(p2.fY);
2532
2533 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2534 (p1y - p0y) * (p2x - p0x));
2535
2536 }
2537 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002538}
2539
reed@google.comc1ea60a2012-01-31 15:15:36 +00002540// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002541static int find_max_y(const SkPoint pts[], int count) {
2542 SkASSERT(count > 0);
2543 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002544 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002545 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002546 SkScalar y = pts[i].fY;
2547 if (y > max) {
2548 max = y;
2549 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002550 }
2551 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002552 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002553}
2554
reed@google.comcabaf1d2012-01-11 21:03:05 +00002555static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2556 int i = index;
2557 for (;;) {
2558 i = (i + inc) % n;
2559 if (i == index) { // we wrapped around, so abort
2560 break;
2561 }
2562 if (pts[index] != pts[i]) { // found a different point, success!
2563 break;
2564 }
2565 }
2566 return i;
2567}
2568
reed@google.comc1ea60a2012-01-31 15:15:36 +00002569/**
2570 * Starting at index, and moving forward (incrementing), find the xmin and
2571 * xmax of the contiguous points that have the same Y.
2572 */
2573static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2574 int* maxIndexPtr) {
2575 const SkScalar y = pts[index].fY;
2576 SkScalar min = pts[index].fX;
2577 SkScalar max = min;
2578 int minIndex = index;
2579 int maxIndex = index;
2580 for (int i = index + 1; i < n; ++i) {
2581 if (pts[i].fY != y) {
2582 break;
2583 }
2584 SkScalar x = pts[i].fX;
2585 if (x < min) {
2586 min = x;
2587 minIndex = i;
2588 } else if (x > max) {
2589 max = x;
2590 maxIndex = i;
2591 }
2592 }
2593 *maxIndexPtr = maxIndex;
2594 return minIndex;
2595}
2596
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002597static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002598 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002599}
2600
reed@google.comac8543f2012-01-30 20:51:25 +00002601/*
2602 * We loop through all contours, and keep the computed cross-product of the
2603 * contour that contained the global y-max. If we just look at the first
2604 * contour, we may find one that is wound the opposite way (correctly) since
2605 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2606 * that is outer most (or at least has the global y-max) before we can consider
2607 * its cross product.
2608 */
reed@google.com69a99432012-01-10 18:00:10 +00002609bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002610 if (kUnknown_Direction != fDirection) {
2611 *dir = static_cast<Direction>(fDirection);
2612 return true;
2613 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002614
2615 // don't want to pay the cost for computing this if it
2616 // is unknown, so we don't call isConvex()
2617 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2618 SkASSERT(kUnknown_Direction == fDirection);
2619 *dir = static_cast<Direction>(fDirection);
2620 return false;
2621 }
reed@google.com69a99432012-01-10 18:00:10 +00002622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002623 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002624
reed@google.comac8543f2012-01-30 20:51:25 +00002625 // initialize with our logical y-min
2626 SkScalar ymax = this->getBounds().fTop;
2627 SkScalar ymaxCross = 0;
2628
reed@google.com69a99432012-01-10 18:00:10 +00002629 for (; !iter.done(); iter.next()) {
2630 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002631 if (n < 3) {
2632 continue;
2633 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002634
reed@google.comcabaf1d2012-01-11 21:03:05 +00002635 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002636 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002637 int index = find_max_y(pts, n);
2638 if (pts[index].fY < ymax) {
2639 continue;
2640 }
2641
2642 // If there is more than 1 distinct point at the y-max, we take the
2643 // x-min and x-max of them and just subtract to compute the dir.
2644 if (pts[(index + 1) % n].fY == pts[index].fY) {
2645 int maxIndex;
2646 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2647 if (minIndex == maxIndex) {
2648 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002649 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002650 SkASSERT(pts[minIndex].fY == pts[index].fY);
2651 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2652 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2653 // we just subtract the indices, and let that auto-convert to
2654 // SkScalar, since we just want - or + to signal the direction.
2655 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002656 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002657 TRY_CROSSPROD:
2658 // Find a next and prev index to use for the cross-product test,
2659 // but we try to find pts that form non-zero vectors from pts[index]
2660 //
2661 // Its possible that we can't find two non-degenerate vectors, so
2662 // we have to guard our search (e.g. all the pts could be in the
2663 // same place).
2664
2665 // we pass n - 1 instead of -1 so we don't foul up % operator by
2666 // passing it a negative LH argument.
2667 int prev = find_diff_pt(pts, index, n, n - 1);
2668 if (prev == index) {
2669 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002670 continue;
2671 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002672 int next = find_diff_pt(pts, index, n, 1);
2673 SkASSERT(next != index);
2674 cross = cross_prod(pts[prev], pts[index], pts[next]);
2675 // if we get a zero and the points are horizontal, then we look at the spread in
2676 // x-direction. We really should continue to walk away from the degeneracy until
2677 // there is a divergence.
2678 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2679 // construct the subtract so we get the correct Direction below
2680 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002681 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002682 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002683
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002684 if (cross) {
2685 // record our best guess so far
2686 ymax = pts[index].fY;
2687 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002688 }
2689 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002690 if (ymaxCross) {
2691 crossToDir(ymaxCross, dir);
2692 fDirection = *dir;
2693 return true;
2694 } else {
2695 return false;
2696 }
reed@google.comac8543f2012-01-30 20:51:25 +00002697}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002698
2699///////////////////////////////////////////////////////////////////////////////
2700
2701static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2702 SkScalar D, SkScalar t) {
2703 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2704}
2705
2706static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2707 SkScalar t) {
2708 SkScalar A = c3 + 3*(c1 - c2) - c0;
2709 SkScalar B = 3*(c2 - c1 - c1 + c0);
2710 SkScalar C = 3*(c1 - c0);
2711 SkScalar D = c0;
2712 return eval_cubic_coeff(A, B, C, D, t);
2713}
2714
2715/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2716 t value such that cubic(t) = target
2717 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002718static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002719 SkScalar target, SkScalar* t) {
2720 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2721 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002722
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002723 SkScalar D = c0 - target;
2724 SkScalar A = c3 + 3*(c1 - c2) - c0;
2725 SkScalar B = 3*(c2 - c1 - c1 + c0);
2726 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2729 SkScalar minT = 0;
2730 SkScalar maxT = SK_Scalar1;
2731 SkScalar mid;
2732 int i;
2733 for (i = 0; i < 16; i++) {
2734 mid = SkScalarAve(minT, maxT);
2735 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2736 if (delta < 0) {
2737 minT = mid;
2738 delta = -delta;
2739 } else {
2740 maxT = mid;
2741 }
2742 if (delta < TOLERANCE) {
2743 break;
2744 }
2745 }
2746 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747}
2748
2749template <size_t N> static void find_minmax(const SkPoint pts[],
2750 SkScalar* minPtr, SkScalar* maxPtr) {
2751 SkScalar min, max;
2752 min = max = pts[0].fX;
2753 for (size_t i = 1; i < N; ++i) {
2754 min = SkMinScalar(min, pts[i].fX);
2755 max = SkMaxScalar(max, pts[i].fX);
2756 }
2757 *minPtr = min;
2758 *maxPtr = max;
2759}
2760
2761static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2762 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002763
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764 int dir = 1;
2765 if (pts[0].fY > pts[3].fY) {
2766 storage[0] = pts[3];
2767 storage[1] = pts[2];
2768 storage[2] = pts[1];
2769 storage[3] = pts[0];
2770 pts = storage;
2771 dir = -1;
2772 }
2773 if (y < pts[0].fY || y >= pts[3].fY) {
2774 return 0;
2775 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002776
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002777 // quickreject or quickaccept
2778 SkScalar min, max;
2779 find_minmax<4>(pts, &min, &max);
2780 if (x < min) {
2781 return 0;
2782 }
2783 if (x > max) {
2784 return dir;
2785 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002786
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002788 SkScalar t;
2789 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2790 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 +00002791 return xt < x ? dir : 0;
2792}
2793
2794static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2795 SkPoint dst[10];
2796 int n = SkChopCubicAtYExtrema(pts, dst);
2797 int w = 0;
2798 for (int i = 0; i <= n; ++i) {
2799 w += winding_mono_cubic(&dst[i * 3], x, y);
2800 }
2801 return w;
2802}
2803
2804static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2805 SkScalar y0 = pts[0].fY;
2806 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002807
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 int dir = 1;
2809 if (y0 > y2) {
2810 SkTSwap(y0, y2);
2811 dir = -1;
2812 }
2813 if (y < y0 || y >= y2) {
2814 return 0;
2815 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // bounds check on X (not required. is it faster?)
2818#if 0
2819 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2820 return 0;
2821 }
2822#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 SkScalar roots[2];
2825 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2826 2 * (pts[1].fY - pts[0].fY),
2827 pts[0].fY - y,
2828 roots);
2829 SkASSERT(n <= 1);
2830 SkScalar xt;
2831 if (0 == n) {
2832 SkScalar mid = SkScalarAve(y0, y2);
2833 // Need [0] and [2] if dir == 1
2834 // and [2] and [0] if dir == -1
2835 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2836 } else {
2837 SkScalar t = roots[0];
2838 SkScalar C = pts[0].fX;
2839 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2840 SkScalar B = 2 * (pts[1].fX - C);
2841 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2842 }
2843 return xt < x ? dir : 0;
2844}
2845
2846static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2847 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2848 if (y0 == y1) {
2849 return true;
2850 }
2851 if (y0 < y1) {
2852 return y1 <= y2;
2853 } else {
2854 return y1 >= y2;
2855 }
2856}
2857
2858static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2859 SkPoint dst[5];
2860 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2863 n = SkChopQuadAtYExtrema(pts, dst);
2864 pts = dst;
2865 }
2866 int w = winding_mono_quad(pts, x, y);
2867 if (n > 0) {
2868 w += winding_mono_quad(&pts[2], x, y);
2869 }
2870 return w;
2871}
2872
2873static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2874 SkScalar x0 = pts[0].fX;
2875 SkScalar y0 = pts[0].fY;
2876 SkScalar x1 = pts[1].fX;
2877 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 int dir = 1;
2882 if (y0 > y1) {
2883 SkTSwap(y0, y1);
2884 dir = -1;
2885 }
2886 if (y < y0 || y >= y1) {
2887 return 0;
2888 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002889
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2891 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002892
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002893 if (SkScalarSignAsInt(cross) == dir) {
2894 dir = 0;
2895 }
2896 return dir;
2897}
2898
reed@google.com4db592c2013-10-30 17:39:43 +00002899static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2900 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2901}
2902
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002903bool SkPath::contains(SkScalar x, SkScalar y) const {
2904 bool isInverse = this->isInverseFillType();
2905 if (this->isEmpty()) {
2906 return isInverse;
2907 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002908
reed@google.com4db592c2013-10-30 17:39:43 +00002909 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002910 return isInverse;
2911 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002912
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002913 SkPath::Iter iter(*this, true);
2914 bool done = false;
2915 int w = 0;
2916 do {
2917 SkPoint pts[4];
2918 switch (iter.next(pts, false)) {
2919 case SkPath::kMove_Verb:
2920 case SkPath::kClose_Verb:
2921 break;
2922 case SkPath::kLine_Verb:
2923 w += winding_line(pts, x, y);
2924 break;
2925 case SkPath::kQuad_Verb:
2926 w += winding_quad(pts, x, y);
2927 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002928 case SkPath::kConic_Verb:
2929 SkASSERT(0);
2930 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002931 case SkPath::kCubic_Verb:
2932 w += winding_cubic(pts, x, y);
2933 break;
2934 case SkPath::kDone_Verb:
2935 done = true;
2936 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002937 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002938 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002939
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002940 switch (this->getFillType()) {
2941 case SkPath::kEvenOdd_FillType:
2942 case SkPath::kInverseEvenOdd_FillType:
2943 w &= 1;
2944 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002945 default:
2946 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002947 }
2948 return SkToBool(w);
2949}