blob: defedd2e5db4026a38f33e38e18310aabfd7da9d [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070012#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
reed026beb52015-06-10 14:23:15 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
141 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
reed026beb52015-06-10 14:23:15 -0700170 fFirstDirection = that.fFirstDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700171 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172}
173
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000174bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000175 // note: don't need to look at isConvex or bounds, since just comparing the
176 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000177 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000178 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 if (this != &that) {
183 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000184 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
reed026beb52015-06-10 14:23:15 -0700187 SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700188 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000189 }
190}
191
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000192static inline bool check_edge_against_rect(const SkPoint& p0,
193 const SkPoint& p1,
194 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700195 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000196 const SkPoint* edgeBegin;
197 SkVector v;
reed026beb52015-06-10 14:23:15 -0700198 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000199 v = p1 - p0;
200 edgeBegin = &p0;
201 } else {
202 v = p0 - p1;
203 edgeBegin = &p1;
204 }
205 if (v.fX || v.fY) {
206 // check the cross product of v with the vec from edgeBegin to each rect corner
207 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
208 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
209 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
210 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
211 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
212 return false;
213 }
214 }
215 return true;
216}
217
218bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
219 // This only handles non-degenerate convex paths currently.
220 if (kConvex_Convexity != this->getConvexity()) {
221 return false;
222 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000223
reed026beb52015-06-10 14:23:15 -0700224 SkPathPriv::FirstDirection direction;
225 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000226 return false;
227 }
228
229 SkPoint firstPt;
230 SkPoint prevPt;
231 RawIter iter(*this);
232 SkPath::Verb verb;
233 SkPoint pts[4];
234 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000235 SkDEBUGCODE(int segmentCount = 0;)
236 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237
238 while ((verb = iter.next(pts)) != kDone_Verb) {
239 int nextPt = -1;
240 switch (verb) {
241 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000242 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000243 SkDEBUGCODE(++moveCnt);
244 firstPt = prevPt = pts[0];
245 break;
246 case kLine_Verb:
247 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000248 SkASSERT(moveCnt && !closeCount);
249 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 break;
251 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000252 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000253 SkASSERT(moveCnt && !closeCount);
254 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000255 nextPt = 2;
256 break;
257 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000258 SkASSERT(moveCnt && !closeCount);
259 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 nextPt = 3;
261 break;
262 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000263 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264 break;
265 default:
266 SkDEBUGFAIL("unknown verb");
267 }
268 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800269 if (SkPath::kConic_Verb == verb) {
270 SkConic orig;
271 orig.set(pts, iter.conicWeight());
272 SkPoint quadPts[5];
273 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
274 SK_ALWAYSBREAK(2 == count);
275
276 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
277 return false;
278 }
279 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
280 return false;
281 }
282 } else {
283 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
284 return false;
285 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 }
287 prevPt = pts[nextPt];
288 }
289 }
290
291 return check_edge_against_rect(prevPt, firstPt, rect, direction);
292}
293
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000294uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000295 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800296#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000297 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
298 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
299#endif
300 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000301}
djsollen@google.come63793a2012-03-21 15:39:03 +0000302
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303void SkPath::reset() {
304 SkDEBUGCODE(this->validate();)
305
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000306 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000307 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308}
309
310void SkPath::rewind() {
311 SkDEBUGCODE(this->validate();)
312
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000313 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000314 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315}
316
reed@google.com7e6c4d12012-05-10 14:05:43 +0000317bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000318 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000319
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000320 if (2 == verbCount) {
321 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
322 if (kLine_Verb == fPathRef->atVerb(1)) {
323 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000324 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000325 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000326 line[0] = pts[0];
327 line[1] = pts[1];
328 }
329 return true;
330 }
331 }
332 return false;
333}
334
caryclark@google.comf1316942011-07-26 19:54:45 +0000335/*
336 Determines if path is a rect by keeping track of changes in direction
337 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000338
caryclark@google.comf1316942011-07-26 19:54:45 +0000339 The direction is computed such that:
340 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000341 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000342 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000343 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000344
caryclark@google.comf1316942011-07-26 19:54:45 +0000345A rectangle cycles up/right/down/left or up/left/down/right.
346
347The test fails if:
348 The path is closed, and followed by a line.
349 A second move creates a new endpoint.
350 A diagonal line is parsed.
351 There's more than four changes of direction.
352 There's a discontinuity on the line (e.g., a move in the middle)
353 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000354 The path contains a quadratic or cubic.
355 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000356 *The rectangle doesn't complete a cycle.
357 *The final point isn't equal to the first point.
358
359 *These last two conditions we relax if we have a 3-edge path that would
360 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000361
caryclark@google.comf1316942011-07-26 19:54:45 +0000362It's OK if the path has:
363 Several colinear line segments composing a rectangle side.
364 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000365
caryclark@google.comf1316942011-07-26 19:54:45 +0000366The direction takes advantage of the corners found since opposite sides
367must travel in opposite directions.
368
369FIXME: Allow colinear quads and cubics to be treated like lines.
370FIXME: If the API passes fill-only, return true if the filled stroke
371 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000372
373 first,last,next direction state-machine:
374 0x1 is set if the segment is horizontal
375 0x2 is set if the segment is moving to the right or down
376 thus:
377 two directions are opposites iff (dirA ^ dirB) == 0x2
378 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000379
caryclark@google.comf1316942011-07-26 19:54:45 +0000380 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000381static int rect_make_dir(SkScalar dx, SkScalar dy) {
382 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
383}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000384bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
385 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000386 int corners = 0;
387 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000388 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000389 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000390 first.set(0, 0);
391 last.set(0, 0);
392 int firstDirection = 0;
393 int lastDirection = 0;
394 int nextDirection = 0;
395 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000396 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700397 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000398 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000399 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700400 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
401 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000402 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000403 savePts = pts;
404 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700406 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 case kLine_Verb: {
408 SkScalar left = last.fX;
409 SkScalar top = last.fY;
410 SkScalar right = pts->fX;
411 SkScalar bottom = pts->fY;
412 ++pts;
413 if (left != right && top != bottom) {
414 return false; // diagonal
415 }
416 if (left == right && top == bottom) {
417 break; // single point on side OK
418 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000419 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000420 if (0 == corners) {
421 firstDirection = nextDirection;
422 first = last;
423 last = pts[-1];
424 corners = 1;
425 closedOrMoved = false;
426 break;
427 }
428 if (closedOrMoved) {
429 return false; // closed followed by a line
430 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000431 if (autoClose && nextDirection == firstDirection) {
432 break; // colinear with first
433 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000434 closedOrMoved = autoClose;
435 if (lastDirection != nextDirection) {
436 if (++corners > 4) {
437 return false; // too many direction changes
438 }
439 }
440 last = pts[-1];
441 if (lastDirection == nextDirection) {
442 break; // colinear segment
443 }
444 // Possible values for corners are 2, 3, and 4.
445 // When corners == 3, nextDirection opposes firstDirection.
446 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000447 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
449 if ((directionCycle ^ turn) != nextDirection) {
450 return false; // direction didn't follow cycle
451 }
452 break;
453 }
454 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000455 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 case kCubic_Verb:
457 return false; // quadratic, cubic not allowed
458 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700459 if (allowPartial && !autoClose && firstDirection) {
460 insertClose = true;
461 *currVerb -= 1; // try move again afterwards
462 goto addMissingClose;
463 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 last = *pts++;
465 closedOrMoved = true;
466 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000467 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000468 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000469 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700473addMissingClose:
474 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 }
476 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000477 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000478 if (!result) {
479 // check if we are just an incomplete rectangle, in which case we can
480 // return true, but not claim to be closed.
481 // e.g.
482 // 3 sided rectangle
483 // 4 sided but the last edge is not long enough to reach the start
484 //
485 SkScalar closeX = first.x() - last.x();
486 SkScalar closeY = first.y() - last.y();
487 if (closeX && closeY) {
488 return false; // we're diagonal, abort (can we ever reach this?)
489 }
490 int closeDirection = rect_make_dir(closeX, closeY);
491 // make sure the close-segment doesn't double-back on itself
492 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
493 result = true;
494 autoClose = false; // we are not closed
495 }
496 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000497 if (savePts) {
498 *ptsPtr = savePts;
499 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000500 if (result && isClosed) {
501 *isClosed = autoClose;
502 }
503 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000504 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000505 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000506 return result;
507}
508
robertphillips4f662e62014-12-29 14:06:51 -0800509bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 SkDEBUGCODE(this->validate();)
511 int currVerb = 0;
512 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800513 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800514 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800515 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800517 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800518 int32_t num = SkToS32(pts - first);
519 if (num) {
520 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800521 } else {
522 // 'pts' isn't updated for open rects
523 *rect = this->getBounds();
524 }
525 }
526 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000527}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000528
caryclark95bc5f32015-04-08 08:34:15 -0700529bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000530 SkDEBUGCODE(this->validate();)
531 int currVerb = 0;
532 const SkPoint* pts = fPathRef->points();
533 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000534 Direction testDirs[2];
535 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000536 return false;
537 }
538 const SkPoint* last = pts;
539 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700540 bool isClosed;
541 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000542 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700543 if (!isClosed) {
544 pts = fPathRef->points() + fPathRef->countPoints();
545 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000546 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 if (testRects[0].contains(testRects[1])) {
548 if (rects) {
549 rects[0] = testRects[0];
550 rects[1] = testRects[1];
551 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000552 if (dirs) {
553 dirs[0] = testDirs[0];
554 dirs[1] = testDirs[1];
555 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 return true;
557 }
558 if (testRects[1].contains(testRects[0])) {
559 if (rects) {
560 rects[0] = testRects[1];
561 rects[1] = testRects[0];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[1];
565 dirs[1] = testDirs[0];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 }
570 return false;
571}
572
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000573int SkPath::countPoints() const {
574 return fPathRef->countPoints();
575}
576
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000577int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 SkDEBUGCODE(this->validate();)
579
580 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000581 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 int count = SkMin32(max, fPathRef->countPoints());
583 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
584 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585}
586
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000587SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
589 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590 }
591 return SkPoint::Make(0, 0);
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countVerbs() const {
595 return fPathRef->countVerbs();
596}
597
598static inline void copy_verbs_reverse(uint8_t* inorderDst,
599 const uint8_t* reversedSrc,
600 int count) {
601 for (int i = 0; i < count; ++i) {
602 inorderDst[i] = reversedSrc[~i];
603 }
604}
605
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606int SkPath::getVerbs(uint8_t dst[], int max) const {
607 SkDEBUGCODE(this->validate();)
608
609 SkASSERT(max >= 0);
610 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = SkMin32(max, fPathRef->countVerbs());
612 copy_verbs_reverse(dst, fPathRef->verbs(), count);
613 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000614}
615
reed@google.com294dd7b2011-10-11 11:58:32 +0000616bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 SkDEBUGCODE(this->validate();)
618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000619 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (count > 0) {
621 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000624 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 if (lastPt) {
627 lastPt->set(0, 0);
628 }
629 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630}
631
caryclarkaec25102015-04-29 08:28:30 -0700632void SkPath::setPt(int index, SkScalar x, SkScalar y) {
633 SkDEBUGCODE(this->validate();)
634
635 int count = fPathRef->countPoints();
636 if (count <= index) {
637 return;
638 } else {
639 SkPathRef::Editor ed(&fPathRef);
640 ed.atPoint(index)->set(x, y);
641 }
642}
643
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644void SkPath::setLastPt(SkScalar x, SkScalar y) {
645 SkDEBUGCODE(this->validate();)
646
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 if (count == 0) {
649 this->moveTo(x, y);
650 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 SkPathRef::Editor ed(&fPathRef);
652 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 }
654}
655
reed@google.com04863fa2011-05-15 04:08:24 +0000656void SkPath::setConvexity(Convexity c) {
657 if (fConvexity != c) {
658 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000659 }
660}
661
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662//////////////////////////////////////////////////////////////////////////////
663// Construction methods
664
reed026beb52015-06-10 14:23:15 -0700665#define DIRTY_AFTER_EDIT \
666 do { \
667 fConvexity = kUnknown_Convexity; \
668 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000669 } while (0)
670
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671void SkPath::incReserve(U16CPU inc) {
672 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674 SkDEBUGCODE(this->validate();)
675}
676
677void SkPath::moveTo(SkScalar x, SkScalar y) {
678 SkDEBUGCODE(this->validate();)
679
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000682 // remember our index
683 fLastMoveToIndex = fPathRef->countPoints();
684
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700686
687 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688}
689
690void SkPath::rMoveTo(SkScalar x, SkScalar y) {
691 SkPoint pt;
692 this->getLastPt(&pt);
693 this->moveTo(pt.fX + x, pt.fY + y);
694}
695
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000696void SkPath::injectMoveToIfNeeded() {
697 if (fLastMoveToIndex < 0) {
698 SkScalar x, y;
699 if (fPathRef->countVerbs() == 0) {
700 x = y = 0;
701 } else {
702 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
703 x = pt.fX;
704 y = pt.fY;
705 }
706 this->moveTo(x, y);
707 }
708}
709
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710void SkPath::lineTo(SkScalar x, SkScalar y) {
711 SkDEBUGCODE(this->validate();)
712
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000713 this->injectMoveToIfNeeded();
714
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000715 SkPathRef::Editor ed(&fPathRef);
716 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717
reed@google.comb54455e2011-05-16 14:16:04 +0000718 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719}
720
721void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000722 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 SkPoint pt;
724 this->getLastPt(&pt);
725 this->lineTo(pt.fX + x, pt.fY + y);
726}
727
728void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
729 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000730
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000731 this->injectMoveToIfNeeded();
732
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000733 SkPathRef::Editor ed(&fPathRef);
734 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 pts[0].set(x1, y1);
736 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000737
reed@google.comb54455e2011-05-16 14:16:04 +0000738 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739}
740
741void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000742 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743 SkPoint pt;
744 this->getLastPt(&pt);
745 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
746}
747
reed@google.com277c3f82013-05-31 15:17:50 +0000748void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
749 SkScalar w) {
750 // check for <= 0 or NaN with this test
751 if (!(w > 0)) {
752 this->lineTo(x2, y2);
753 } else if (!SkScalarIsFinite(w)) {
754 this->lineTo(x1, y1);
755 this->lineTo(x2, y2);
756 } else if (SK_Scalar1 == w) {
757 this->quadTo(x1, y1, x2, y2);
758 } else {
759 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000760
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000761 this->injectMoveToIfNeeded();
762
reed@google.com277c3f82013-05-31 15:17:50 +0000763 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000764 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000765 pts[0].set(x1, y1);
766 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000767
reed@google.com277c3f82013-05-31 15:17:50 +0000768 DIRTY_AFTER_EDIT;
769 }
770}
771
772void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
773 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000774 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000775 SkPoint pt;
776 this->getLastPt(&pt);
777 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
778}
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
781 SkScalar x3, SkScalar y3) {
782 SkDEBUGCODE(this->validate();)
783
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000784 this->injectMoveToIfNeeded();
785
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000786 SkPathRef::Editor ed(&fPathRef);
787 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 pts[0].set(x1, y1);
789 pts[1].set(x2, y2);
790 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791
reed@google.comb54455e2011-05-16 14:16:04 +0000792 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793}
794
795void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
796 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000797 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 SkPoint pt;
799 this->getLastPt(&pt);
800 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
801 pt.fX + x3, pt.fY + y3);
802}
803
804void SkPath::close() {
805 SkDEBUGCODE(this->validate();)
806
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000807 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000809 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 case kLine_Verb:
811 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000812 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000814 case kMove_Verb: {
815 SkPathRef::Editor ed(&fPathRef);
816 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000818 }
reed@google.com277c3f82013-05-31 15:17:50 +0000819 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000820 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000821 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000822 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000823 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000824 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 }
826 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000827
828 // signal that we need a moveTo to follow us (unless we're done)
829#if 0
830 if (fLastMoveToIndex >= 0) {
831 fLastMoveToIndex = ~fLastMoveToIndex;
832 }
833#else
834 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
835#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836}
837
838///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000839
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000840static void assert_known_direction(int dir) {
841 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
842}
843
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844void SkPath::addRect(const SkRect& rect, Direction dir) {
845 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
846}
847
848void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
849 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000850 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700851 fFirstDirection = this->hasOnlyMoveTos() ?
852 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000853 SkAutoDisableDirectionCheck addc(this);
854
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
856
857 this->incReserve(5);
858
859 this->moveTo(left, top);
860 if (dir == kCCW_Direction) {
861 this->lineTo(left, bottom);
862 this->lineTo(right, bottom);
863 this->lineTo(right, top);
864 } else {
865 this->lineTo(right, top);
866 this->lineTo(right, bottom);
867 this->lineTo(left, bottom);
868 }
869 this->close();
870}
871
reed@google.com744faba2012-05-29 19:54:52 +0000872void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
873 SkDEBUGCODE(this->validate();)
874 if (count <= 0) {
875 return;
876 }
877
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000878 fLastMoveToIndex = fPathRef->countPoints();
879
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000880 // +close makes room for the extra kClose_Verb
881 SkPathRef::Editor ed(&fPathRef, count+close, count);
882
883 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000884 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000885 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
886 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000887 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000888
reed@google.com744faba2012-05-29 19:54:52 +0000889 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000890 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800891 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000892 }
893
reed@google.com744faba2012-05-29 19:54:52 +0000894 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000895 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000896}
897
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000898#include "SkGeometry.h"
899
reedf90ea012015-01-29 12:03:58 -0800900static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
901 SkPoint* pt) {
902 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000903 // Chrome uses this path to move into and out of ovals. If not
904 // treated as a special case the moves can distort the oval's
905 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800906 pt->set(oval.fRight, oval.centerY());
907 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000908 } else if (0 == oval.width() && 0 == oval.height()) {
909 // Chrome will sometimes create 0 radius round rects. Having degenerate
910 // quad segments in the path prevents the path from being recognized as
911 // a rect.
912 // TODO: optimizing the case where only one of width or height is zero
913 // should also be considered. This case, however, doesn't seem to be
914 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800915 pt->set(oval.fRight, oval.fTop);
916 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000917 }
reedf90ea012015-01-29 12:03:58 -0800918 return false;
919}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000920
reedd5d27d92015-02-09 13:54:43 -0800921// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
922//
923static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
924 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
925 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
926 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000927
928 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800929 loss in radians-conversion and/or sin/cos, we may end up with coincident
930 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
931 of drawing a nearly complete circle (good).
932 e.g. canvas.drawArc(0, 359.99, ...)
933 -vs- canvas.drawArc(0, 359.9, ...)
934 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000935 */
reedd5d27d92015-02-09 13:54:43 -0800936 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000937 SkScalar sw = SkScalarAbs(sweepAngle);
938 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
939 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
940 // make a guess at a tiny angle (in radians) to tweak by
941 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
942 // not sure how much will be enough, so we use a loop
943 do {
944 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800945 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
946 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000947 }
948 }
reedd5d27d92015-02-09 13:54:43 -0800949 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
950}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000951
reed9e779d42015-02-17 11:43:14 -0800952/**
953 * If this returns 0, then the caller should just line-to the singlePt, else it should
954 * ignore singlePt and append the specified number of conics.
955 */
reedd5d27d92015-02-09 13:54:43 -0800956static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800957 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
958 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800959 SkMatrix matrix;
960
961 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
962 matrix.postTranslate(oval.centerX(), oval.centerY());
963
reed9e779d42015-02-17 11:43:14 -0800964 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
965 if (0 == count) {
966 matrix.mapXY(start.x(), start.y(), singlePt);
967 }
968 return count;
reedd5d27d92015-02-09 13:54:43 -0800969}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000970
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
reed@google.com4ed0fb72012-12-12 20:48:18 +0000978void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000979 assert_known_direction(dir);
980
981 if (rrect.isEmpty()) {
982 return;
983 }
984
reed@google.com4ed0fb72012-12-12 20:48:18 +0000985 const SkRect& bounds = rrect.getBounds();
986
987 if (rrect.isRect()) {
988 this->addRect(bounds, dir);
989 } else if (rrect.isOval()) {
990 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000991 } else {
reed026beb52015-06-10 14:23:15 -0700992 fFirstDirection = this->hasOnlyMoveTos() ?
993 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000994
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000995 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000996 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000997
reed1b28a3a2014-12-17 14:42:09 -0800998 const SkScalar L = bounds.fLeft;
999 const SkScalar T = bounds.fTop;
1000 const SkScalar R = bounds.fRight;
1001 const SkScalar B = bounds.fBottom;
1002 const SkScalar W = SK_ScalarRoot2Over2;
1003
1004 this->incReserve(13);
1005 if (kCW_Direction == dir) {
1006 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1007
1008 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1009 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1010
1011 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1012 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1013
1014 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1015 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1016
1017 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1018 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1019 } else {
1020 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1021
1022 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1023 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1024
1025 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1026 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1027
1028 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1029 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1030
1031 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1032 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1033 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001034 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001035 }
reed5bcbe912014-12-15 12:28:33 -08001036 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001037}
1038
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001039bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001040 int count = fPathRef->countVerbs();
1041 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1042 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001043 if (*verbs == kLine_Verb ||
1044 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001045 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001046 *verbs == kCubic_Verb) {
1047 return false;
1048 }
1049 ++verbs;
1050 }
1051 return true;
1052}
1053
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001054void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1055 Direction dir) {
1056 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001057
humper@google.com75e3ca12013-04-08 21:44:11 +00001058 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001059 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001060 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001061 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001062 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1063 return;
1064 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001065
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001066 SkRRect rrect;
1067 rrect.setRectXY(rect, rx, ry);
1068 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001069}
1070
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001072 assert_known_direction(dir);
1073
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074 /* If addOval() is called after previous moveTo(),
1075 this path is still marked as an oval. This is used to
1076 fit into WebKit's calling sequences.
1077 We can't simply check isEmpty() in this case, as additional
1078 moveTo() would mark the path non empty.
1079 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001080 bool isOval = hasOnlyMoveTos();
1081 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001082 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001083 } else {
reed026beb52015-06-10 14:23:15 -07001084 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001085 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001086
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001087 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001088
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089 SkAutoPathBoundsUpdate apbu(this, oval);
1090
reed220f9262014-12-17 08:21:04 -08001091 const SkScalar L = oval.fLeft;
1092 const SkScalar T = oval.fTop;
1093 const SkScalar R = oval.fRight;
1094 const SkScalar B = oval.fBottom;
1095 const SkScalar cx = oval.centerX();
1096 const SkScalar cy = oval.centerY();
1097 const SkScalar weight = SK_ScalarRoot2Over2;
1098
1099 this->incReserve(9); // move + 4 conics
1100 this->moveTo(R, cy);
1101 if (dir == kCCW_Direction) {
1102 this->conicTo(R, T, cx, T, weight);
1103 this->conicTo(L, T, L, cy, weight);
1104 this->conicTo(L, B, cx, B, weight);
1105 this->conicTo(R, B, R, cy, weight);
1106 } else {
1107 this->conicTo(R, B, cx, B, weight);
1108 this->conicTo(L, B, L, cy, weight);
1109 this->conicTo(L, T, cx, T, weight);
1110 this->conicTo(R, T, R, cy, weight);
1111 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113
robertphillips@google.com466310d2013-12-03 16:43:54 +00001114 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001115
robertphillips@google.com466310d2013-12-03 16:43:54 +00001116 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001117}
1118
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1120 if (r > 0) {
1121 SkRect rect;
1122 rect.set(x - r, y - r, x + r, y + r);
1123 this->addOval(rect, dir);
1124 }
1125}
1126
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1128 bool forceMoveTo) {
1129 if (oval.width() < 0 || oval.height() < 0) {
1130 return;
1131 }
1132
reedf90ea012015-01-29 12:03:58 -08001133 if (fPathRef->countVerbs() == 0) {
1134 forceMoveTo = true;
1135 }
1136
1137 SkPoint lonePt;
1138 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1139 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1140 return;
1141 }
1142
reedd5d27d92015-02-09 13:54:43 -08001143 SkVector startV, stopV;
1144 SkRotationDirection dir;
1145 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1146
reed9e779d42015-02-17 11:43:14 -08001147 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001148 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001149 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001150 if (count) {
1151 this->incReserve(count * 2 + 1);
1152 const SkPoint& pt = conics[0].fPts[0];
1153 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1154 for (int i = 0; i < count; ++i) {
1155 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1156 }
reed9e779d42015-02-17 11:43:14 -08001157 } else {
1158 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160}
1161
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001162void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 if (oval.isEmpty() || 0 == sweepAngle) {
1164 return;
1165 }
1166
1167 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1168
1169 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1170 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001171 } else {
1172 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174}
1175
1176/*
1177 Need to handle the case when the angle is sharp, and our computed end-points
1178 for the arc go behind pt1 and/or p2...
1179*/
reedc7789042015-01-29 12:59:11 -08001180void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001181 if (radius == 0) {
1182 this->lineTo(x1, y1);
1183 return;
1184 }
1185
1186 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001187
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 // need to know our prev pt so we can construct tangent vectors
1189 {
1190 SkPoint start;
1191 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001192 // Handle degenerate cases by adding a line to the first point and
1193 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 before.setNormalize(x1 - start.fX, y1 - start.fY);
1195 after.setNormalize(x2 - x1, y2 - y1);
1196 }
reed@google.comabf15c12011-01-18 20:35:51 +00001197
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 SkScalar cosh = SkPoint::DotProduct(before, after);
1199 SkScalar sinh = SkPoint::CrossProduct(before, after);
1200
1201 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001202 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 return;
1204 }
reed@google.comabf15c12011-01-18 20:35:51 +00001205
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1207 if (dist < 0) {
1208 dist = -dist;
1209 }
1210
1211 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1212 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1213 SkRotationDirection arcDir;
1214
1215 // now turn before/after into normals
1216 if (sinh > 0) {
1217 before.rotateCCW();
1218 after.rotateCCW();
1219 arcDir = kCW_SkRotationDirection;
1220 } else {
1221 before.rotateCW();
1222 after.rotateCW();
1223 arcDir = kCCW_SkRotationDirection;
1224 }
1225
1226 SkMatrix matrix;
1227 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 matrix.setScale(radius, radius);
1230 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1231 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001234
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 this->incReserve(count);
1236 // [xx,yy] == pts[0]
1237 this->lineTo(xx, yy);
1238 for (int i = 1; i < count; i += 2) {
1239 this->quadTo(pts[i], pts[i+1]);
1240 }
1241}
1242
1243///////////////////////////////////////////////////////////////////////////////
1244
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001245void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 SkMatrix matrix;
1247
1248 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001249 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250}
1251
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001252void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001253 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001255 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 SkPoint pts[4];
1257 Verb verb;
1258
1259 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001260 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 while ((verb = iter.next(pts)) != kDone_Verb) {
1262 switch (verb) {
1263 case kMove_Verb:
1264 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001265 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1266 injectMoveToIfNeeded(); // In case last contour is closed
1267 this->lineTo(pts[0]);
1268 } else {
1269 this->moveTo(pts[0]);
1270 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 break;
1272 case kLine_Verb:
1273 proc(matrix, &pts[1], &pts[1], 1);
1274 this->lineTo(pts[1]);
1275 break;
1276 case kQuad_Verb:
1277 proc(matrix, &pts[1], &pts[1], 2);
1278 this->quadTo(pts[1], pts[2]);
1279 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001280 case kConic_Verb:
1281 proc(matrix, &pts[1], &pts[1], 2);
1282 this->conicTo(pts[1], pts[2], iter.conicWeight());
1283 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 case kCubic_Verb:
1285 proc(matrix, &pts[1], &pts[1], 3);
1286 this->cubicTo(pts[1], pts[2], pts[3]);
1287 break;
1288 case kClose_Verb:
1289 this->close();
1290 break;
1291 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001292 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001294 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 }
1296}
1297
1298///////////////////////////////////////////////////////////////////////////////
1299
reed@google.com277c3f82013-05-31 15:17:50 +00001300static int pts_in_verb(unsigned verb) {
1301 static const uint8_t gPtsInVerb[] = {
1302 1, // kMove
1303 1, // kLine
1304 2, // kQuad
1305 2, // kConic
1306 3, // kCubic
1307 0, // kClose
1308 0 // kDone
1309 };
1310
1311 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1312 return gPtsInVerb[verb];
1313}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315// ignore the last point of the 1st contour
1316void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001317 int i, vcount = path.fPathRef->countVerbs();
1318 // exit early if the path is empty, or just has a moveTo.
1319 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 return;
1321 }
1322
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001323 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001325 const uint8_t* verbs = path.fPathRef->verbs();
1326 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001327 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001329 SkASSERT(verbs[~0] == kMove_Verb);
1330 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001331 unsigned v = verbs[~i];
1332 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 if (n == 0) {
1334 break;
1335 }
1336 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001337 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 }
1339
1340 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001341 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 case kLine_Verb:
1343 this->lineTo(pts[-1].fX, pts[-1].fY);
1344 break;
1345 case kQuad_Verb:
1346 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1347 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001348 case kConic_Verb:
1349 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1350 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 case kCubic_Verb:
1352 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1353 pts[-3].fX, pts[-3].fY);
1354 break;
1355 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001356 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 break;
1358 }
reed@google.com277c3f82013-05-31 15:17:50 +00001359 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 }
1361}
1362
reed@google.com63d73742012-01-10 15:33:12 +00001363void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001364 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001365
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001366 const SkPoint* pts = src.fPathRef->pointsEnd();
1367 // we will iterator through src's verbs backwards
1368 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1369 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001370 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001371
1372 bool needMove = true;
1373 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001374 while (verbs < verbsEnd) {
1375 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001376 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001377
1378 if (needMove) {
1379 --pts;
1380 this->moveTo(pts->fX, pts->fY);
1381 needMove = false;
1382 }
1383 pts -= n;
1384 switch (v) {
1385 case kMove_Verb:
1386 if (needClose) {
1387 this->close();
1388 needClose = false;
1389 }
1390 needMove = true;
1391 pts += 1; // so we see the point in "if (needMove)" above
1392 break;
1393 case kLine_Verb:
1394 this->lineTo(pts[0]);
1395 break;
1396 case kQuad_Verb:
1397 this->quadTo(pts[1], pts[0]);
1398 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001399 case kConic_Verb:
1400 this->conicTo(pts[1], pts[0], *--conicWeights);
1401 break;
reed@google.com63d73742012-01-10 15:33:12 +00001402 case kCubic_Verb:
1403 this->cubicTo(pts[2], pts[1], pts[0]);
1404 break;
1405 case kClose_Verb:
1406 needClose = true;
1407 break;
1408 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001409 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001410 }
1411 }
1412}
1413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414///////////////////////////////////////////////////////////////////////////////
1415
1416void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1417 SkMatrix matrix;
1418
1419 matrix.setTranslate(dx, dy);
1420 this->transform(matrix, dst);
1421}
1422
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1424 int level = 2) {
1425 if (--level >= 0) {
1426 SkPoint tmp[7];
1427
1428 SkChopCubicAtHalf(pts, tmp);
1429 subdivide_cubic_to(path, &tmp[0], level);
1430 subdivide_cubic_to(path, &tmp[3], level);
1431 } else {
1432 path->cubicTo(pts[1], pts[2], pts[3]);
1433 }
1434}
1435
1436void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1437 SkDEBUGCODE(this->validate();)
1438 if (dst == NULL) {
1439 dst = (SkPath*)this;
1440 }
1441
tomhudson@google.com8d430182011-06-06 19:11:19 +00001442 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 SkPath tmp;
1444 tmp.fFillType = fFillType;
1445
1446 SkPath::Iter iter(*this, false);
1447 SkPoint pts[4];
1448 SkPath::Verb verb;
1449
reed@google.com4a3b7142012-05-16 17:16:46 +00001450 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 switch (verb) {
1452 case kMove_Verb:
1453 tmp.moveTo(pts[0]);
1454 break;
1455 case kLine_Verb:
1456 tmp.lineTo(pts[1]);
1457 break;
1458 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001459 // promote the quad to a conic
1460 tmp.conicTo(pts[1], pts[2],
1461 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001463 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001464 tmp.conicTo(pts[1], pts[2],
1465 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001466 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 case kCubic_Verb:
1468 subdivide_cubic_to(&tmp, pts);
1469 break;
1470 case kClose_Verb:
1471 tmp.close();
1472 break;
1473 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001474 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 break;
1476 }
1477 }
1478
1479 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 SkPathRef::Editor ed(&dst->fPathRef);
1481 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001482 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1485
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001488 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001489 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001491
reed026beb52015-06-10 14:23:15 -07001492 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1493 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001494 } else {
1495 SkScalar det2x2 =
1496 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1497 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1498 if (det2x2 < 0) {
reed026beb52015-06-10 14:23:15 -07001499 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection((SkPathPriv::FirstDirection)fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001500 } else if (det2x2 > 0) {
reed026beb52015-06-10 14:23:15 -07001501 dst->fFirstDirection = fFirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001502 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001503 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001504 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001505 }
1506 }
1507
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 SkDEBUGCODE(dst->validate();)
1509 }
1510}
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512///////////////////////////////////////////////////////////////////////////////
1513///////////////////////////////////////////////////////////////////////////////
1514
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001515enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001516 kEmptyContour_SegmentState, // The current contour is empty. We may be
1517 // starting processing or we may have just
1518 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001519 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1520 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1521 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522};
1523
1524SkPath::Iter::Iter() {
1525#ifdef SK_DEBUG
1526 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001527 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001529 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001530 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531#endif
1532 // need to init enough to make next() harmlessly return kDone_Verb
1533 fVerbs = NULL;
1534 fVerbStop = NULL;
1535 fNeedClose = false;
1536}
1537
1538SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1539 this->setPath(path, forceClose);
1540}
1541
1542void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001543 fPts = path.fPathRef->points();
1544 fVerbs = path.fPathRef->verbs();
1545 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001546 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001547 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001548 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 fForceClose = SkToU8(forceClose);
1550 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001551 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552}
1553
1554bool SkPath::Iter::isClosedContour() const {
1555 if (fVerbs == NULL || fVerbs == fVerbStop) {
1556 return false;
1557 }
1558 if (fForceClose) {
1559 return true;
1560 }
1561
1562 const uint8_t* verbs = fVerbs;
1563 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 if (kMove_Verb == *(verbs - 1)) {
1566 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
1568
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 while (verbs > stop) {
1570 // verbs points one beyond the current verb, decrement first.
1571 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (kMove_Verb == v) {
1573 break;
1574 }
1575 if (kClose_Verb == v) {
1576 return true;
1577 }
1578 }
1579 return false;
1580}
1581
1582SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001583 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001585 // A special case: if both points are NaN, SkPoint::operation== returns
1586 // false, but the iterator expects that they are treated as the same.
1587 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001588 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1589 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001590 return kClose_Verb;
1591 }
1592
reed@google.com9e25dbf2012-05-15 17:05:38 +00001593 pts[0] = fLastPt;
1594 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 fLastPt = fMoveTo;
1596 fCloseLine = true;
1597 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001598 } else {
1599 pts[0] = fMoveTo;
1600 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602}
1603
reed@google.com9e25dbf2012-05-15 17:05:38 +00001604const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605 if (fSegmentState == kAfterMove_SegmentState) {
1606 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001607 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001608 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1611 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001612 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614}
1615
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001616void SkPath::Iter::consumeDegenerateSegments() {
1617 // We need to step over anything that will not move the current draw point
1618 // forward before the next move is seen
1619 const uint8_t* lastMoveVerb = 0;
1620 const SkPoint* lastMovePt = 0;
robertphillipsb8de1f42015-02-23 11:17:01 -08001621 const SkScalar* lastMoveWeight = NULL;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001622 SkPoint lastPt = fLastPt;
1623 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001624 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001625 switch (verb) {
1626 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001627 // Keep a record of this most recent move
1628 lastMoveVerb = fVerbs;
1629 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001630 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001631 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001632 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001633 fPts++;
1634 break;
1635
1636 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001637 // A close when we are in a segment is always valid except when it
1638 // follows a move which follows a segment.
1639 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001640 return;
1641 }
1642 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001643 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001644 break;
1645
1646 case kLine_Verb:
1647 if (!IsLineDegenerate(lastPt, fPts[0])) {
1648 if (lastMoveVerb) {
1649 fVerbs = lastMoveVerb;
1650 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001651 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001652 return;
1653 }
1654 return;
1655 }
1656 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001657 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001658 fPts++;
1659 break;
1660
reed@google.com277c3f82013-05-31 15:17:50 +00001661 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 case kQuad_Verb:
1663 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1664 if (lastMoveVerb) {
1665 fVerbs = lastMoveVerb;
1666 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001667 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001668 return;
1669 }
1670 return;
1671 }
1672 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001673 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001674 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001675 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001676 break;
1677
1678 case kCubic_Verb:
1679 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1680 if (lastMoveVerb) {
1681 fVerbs = lastMoveVerb;
1682 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001683 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001684 return;
1685 }
1686 return;
1687 }
1688 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001689 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001690 fPts += 3;
1691 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001692
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001693 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001694 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695 }
1696 }
1697}
1698
reed@google.com4a3b7142012-05-16 17:16:46 +00001699SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001700 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001703 // Close the curve if requested and if there is some curve to close
1704 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001705 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 return kLine_Verb;
1707 }
1708 fNeedClose = false;
1709 return kClose_Verb;
1710 }
1711 return kDone_Verb;
1712 }
1713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001714 // fVerbs is one beyond the current verb, decrement first
1715 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001716 const SkPoint* SK_RESTRICT srcPts = fPts;
1717 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718
1719 switch (verb) {
1720 case kMove_Verb:
1721 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723 verb = this->autoClose(pts);
1724 if (verb == kClose_Verb) {
1725 fNeedClose = false;
1726 }
1727 return (Verb)verb;
1728 }
1729 if (fVerbs == fVerbStop) { // might be a trailing moveto
1730 return kDone_Verb;
1731 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001732 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001733 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001735 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 fNeedClose = fForceClose;
1738 break;
1739 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001740 pts[0] = this->cons_moveTo();
1741 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 fLastPt = srcPts[0];
1743 fCloseLine = false;
1744 srcPts += 1;
1745 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001746 case kConic_Verb:
1747 fConicWeights += 1;
1748 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001750 pts[0] = this->cons_moveTo();
1751 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752 fLastPt = srcPts[1];
1753 srcPts += 2;
1754 break;
1755 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001756 pts[0] = this->cons_moveTo();
1757 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 fLastPt = srcPts[2];
1759 srcPts += 3;
1760 break;
1761 case kClose_Verb:
1762 verb = this->autoClose(pts);
1763 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 } else {
1766 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001767 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 break;
1771 }
1772 fPts = srcPts;
1773 return (Verb)verb;
1774}
1775
1776///////////////////////////////////////////////////////////////////////////////
1777
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001778SkPath::RawIter::RawIter() {
1779#ifdef SK_DEBUG
1780 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001781 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001782#endif
1783 // need to init enough to make next() harmlessly return kDone_Verb
1784 fVerbs = NULL;
1785 fVerbStop = NULL;
1786}
1787
1788SkPath::RawIter::RawIter(const SkPath& path) {
1789 this->setPath(path);
1790}
1791
1792void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001793 fPts = path.fPathRef->points();
1794 fVerbs = path.fPathRef->verbs();
1795 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001796 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001797}
1798
1799SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001800 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001801 if (fVerbs == fVerbStop) {
1802 return kDone_Verb;
1803 }
1804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001805 // fVerbs points one beyond next verb so decrement first.
1806 unsigned verb = *(--fVerbs);
1807 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001808
1809 switch (verb) {
1810 case kMove_Verb:
reed6e434652015-05-27 19:53:25 -07001811 pts[0] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001812 srcPts += 1;
1813 break;
1814 case kLine_Verb:
reedb5615812015-05-13 07:55:48 -07001815 pts[0] = srcPts[-1];
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001816 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001817 srcPts += 1;
1818 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001819 case kConic_Verb:
1820 fConicWeights += 1;
1821 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001822 case kQuad_Verb:
reedb5615812015-05-13 07:55:48 -07001823 pts[0] = srcPts[-1];
1824 pts[1] = srcPts[0];
1825 pts[2] = srcPts[1];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001826 srcPts += 2;
1827 break;
1828 case kCubic_Verb:
reedb5615812015-05-13 07:55:48 -07001829 pts[0] = srcPts[-1];
1830 pts[1] = srcPts[0];
1831 pts[2] = srcPts[1];
1832 pts[3] = srcPts[2];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001833 srcPts += 3;
1834 break;
1835 case kClose_Verb:
reed6e434652015-05-27 19:53:25 -07001836 break;
1837 case kDone_Verb:
1838 SkASSERT(fVerbs == fVerbStop);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001839 break;
1840 }
1841 fPts = srcPts;
1842 return (Verb)verb;
1843}
1844
1845///////////////////////////////////////////////////////////////////////////////
1846
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001848 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849*/
1850
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001851size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 SkDEBUGCODE(this->validate();)
1853
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001854 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001855 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001856 return SkAlign4(byteCount);
1857 }
1858
1859 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001860
robertphillips@google.com466310d2013-12-03 16:43:54 +00001861 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001862 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001863 (fFirstDirection << kDirection_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001864 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001865
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001866 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001869
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001870 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001871 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872}
1873
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001874size_t SkPath::readFromMemory(const void* storage, size_t length) {
1875 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001876
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001877 int32_t packed;
1878 if (!buffer.readS32(&packed)) {
1879 return 0;
1880 }
1881
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001882 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1883 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed026beb52015-06-10 14:23:15 -07001884 fFirstDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001885 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001886 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001887
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001888 size_t sizeRead = 0;
1889 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001890 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001891 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001892 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001893 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001894 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001895 // If the buffer is not valid, pathRef should be NULL
1896 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001897 }
1898 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899}
1900
1901///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902
reede05fed02014-12-15 07:59:53 -08001903#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001904#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001905
reed@google.com51bbe752013-01-17 22:07:50 +00001906static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001907 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001908 str->append(label);
1909 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001910
reed@google.com51bbe752013-01-17 22:07:50 +00001911 const SkScalar* values = &pts[0].fX;
1912 count *= 2;
1913
1914 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001915 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001916 if (i < count - 1) {
1917 str->append(", ");
1918 }
1919 }
reed@google.com277c3f82013-05-31 15:17:50 +00001920 if (conicWeight >= 0) {
1921 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001922 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001923 }
caryclark08fa28c2014-10-23 13:08:56 -07001924 str->append(");");
reede05fed02014-12-15 07:59:53 -08001925 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001926 str->append(" // ");
1927 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001928 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001929 if (i < count - 1) {
1930 str->append(", ");
1931 }
1932 }
1933 if (conicWeight >= 0) {
1934 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001935 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001936 }
1937 }
1938 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001939}
1940
caryclarke9562592014-09-15 09:26:09 -07001941void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001942 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943 Iter iter(*this, forceClose);
1944 SkPoint pts[4];
1945 Verb verb;
1946
caryclark66a5d8b2014-06-24 08:30:15 -07001947 if (!wStream) {
1948 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1949 }
reed@google.com51bbe752013-01-17 22:07:50 +00001950 SkString builder;
1951
reed@google.com4a3b7142012-05-16 17:16:46 +00001952 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953 switch (verb) {
1954 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001955 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 break;
1957 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001958 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 break;
1960 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001961 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001963 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001964 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001965 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001967 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 break;
1969 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001970 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001971 break;
1972 default:
1973 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1974 verb = kDone_Verb; // stop the loop
1975 break;
1976 }
caryclark1049f122015-04-20 08:31:59 -07001977 if (!wStream && builder.size()) {
1978 SkDebugf("%s", builder.c_str());
1979 builder.reset();
1980 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 }
caryclark66a5d8b2014-06-24 08:30:15 -07001982 if (wStream) {
1983 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07001984 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985}
1986
reed@android.come522ca52009-11-23 20:10:41 +00001987void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07001988 this->dump(NULL, false, false);
1989}
1990
1991void SkPath::dumpHex() const {
1992 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00001993}
1994
1995#ifdef SK_DEBUG
1996void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00001997 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00001998
djsollen@google.com077348c2012-10-22 20:23:32 +00001999#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002000 if (!fBoundsIsDirty) {
2001 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002002
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002003 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002004 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002005
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002006 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002007 // if we're empty, fBounds may be empty but translated, so we can't
2008 // necessarily compare to bounds directly
2009 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2010 // be [2, 2, 2, 2]
2011 SkASSERT(bounds.isEmpty());
2012 SkASSERT(fBounds.isEmpty());
2013 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002014 if (bounds.isEmpty()) {
2015 SkASSERT(fBounds.isEmpty());
2016 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002017 if (!fBounds.isEmpty()) {
2018 SkASSERT(fBounds.contains(bounds));
2019 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002020 }
reed@android.come522ca52009-11-23 20:10:41 +00002021 }
2022 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002023#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002024}
djsollen@google.com077348c2012-10-22 20:23:32 +00002025#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002026
reed@google.com04863fa2011-05-15 04:08:24 +00002027///////////////////////////////////////////////////////////////////////////////
2028
reed@google.com0b7b9822011-05-16 12:29:27 +00002029static int sign(SkScalar x) { return x < 0; }
2030#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002031
robertphillipsc506e302014-09-16 09:43:31 -07002032enum DirChange {
2033 kLeft_DirChange,
2034 kRight_DirChange,
2035 kStraight_DirChange,
2036 kBackwards_DirChange,
2037
2038 kInvalid_DirChange
2039};
2040
2041
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002042static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002043 // The error epsilon was empirically derived; worse case round rects
2044 // with a mid point outset by 2x float epsilon in tests had an error
2045 // of 12.
2046 const int epsilon = 16;
2047 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2048 return false;
2049 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002050 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002051 int aBits = SkFloatAs2sCompliment(compA);
2052 int bBits = SkFloatAs2sCompliment(compB);
2053 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002054}
2055
caryclarkb4216502015-03-02 13:02:34 -08002056static bool approximately_zero_when_compared_to(double x, double y) {
2057 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002058}
2059
caryclarkb4216502015-03-02 13:02:34 -08002060
reed@google.com04863fa2011-05-15 04:08:24 +00002061// only valid for a single contour
2062struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002063 Convexicator()
2064 : fPtCount(0)
2065 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002066 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002067 , fIsFinite(true)
2068 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002069 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002070 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002071 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002072 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002073 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002074 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002075
2076 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002077 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002078 }
2079
2080 SkPath::Convexity getConvexity() const { return fConvexity; }
2081
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002082 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002083 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002084
reed@google.com04863fa2011-05-15 04:08:24 +00002085 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002086 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002087 return;
2088 }
2089
2090 if (0 == fPtCount) {
2091 fCurrPt = pt;
2092 ++fPtCount;
2093 } else {
2094 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002095 SkScalar lengthSqd = vec.lengthSqd();
2096 if (!SkScalarIsFinite(lengthSqd)) {
2097 fIsFinite = false;
2098 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
caryclarkb4216502015-03-02 13:02:34 -08002099 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002100 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002101 fCurrPt = pt;
2102 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002103 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002104 } else {
2105 SkASSERT(fPtCount > 2);
2106 this->addVec(vec);
2107 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002108
reed@google.com85b6e392011-05-15 20:25:17 +00002109 int sx = sign(vec.fX);
2110 int sy = sign(vec.fY);
2111 fDx += (sx != fSx);
2112 fDy += (sy != fSy);
2113 fSx = sx;
2114 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002115
reed@google.com85b6e392011-05-15 20:25:17 +00002116 if (fDx > 3 || fDy > 3) {
2117 fConvexity = SkPath::kConcave_Convexity;
2118 }
reed@google.com04863fa2011-05-15 04:08:24 +00002119 }
2120 }
2121 }
2122
2123 void close() {
2124 if (fPtCount > 2) {
2125 this->addVec(fFirstVec);
2126 }
2127 }
2128
caryclarkb4216502015-03-02 13:02:34 -08002129 DirChange directionChange(const SkVector& curVec) {
2130 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2131
2132 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2133 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2134 largest = SkTMax(largest, -smallest);
2135
2136 if (!almost_equal(largest, largest + cross)) {
2137 int sign = SkScalarSignAsInt(cross);
2138 if (sign) {
2139 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2140 }
2141 }
2142
2143 if (cross) {
2144 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2145 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2146 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2147 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2148 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2149 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2150 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2151 if (sign) {
2152 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2153 }
2154 }
2155 }
2156
2157 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2158 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2159 fLastVec.dot(curVec) < 0.0f) {
2160 return kBackwards_DirChange;
2161 }
2162
2163 return kStraight_DirChange;
2164 }
2165
2166
caryclarkd3d1a982014-12-08 04:57:38 -08002167 bool isFinite() const {
2168 return fIsFinite;
2169 }
2170
caryclark5ccef572015-03-02 10:07:56 -08002171 void setCurve(bool isCurve) {
2172 fIsCurve = isCurve;
2173 }
2174
reed@google.com04863fa2011-05-15 04:08:24 +00002175private:
2176 void addVec(const SkVector& vec) {
2177 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002178 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002179 switch (dir) {
2180 case kLeft_DirChange: // fall through
2181 case kRight_DirChange:
2182 if (kInvalid_DirChange == fExpectedDir) {
2183 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002184 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2185 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002186 } else if (dir != fExpectedDir) {
2187 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002188 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002189 }
2190 fLastVec = vec;
2191 break;
2192 case kStraight_DirChange:
2193 break;
2194 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002195 if (fIsCurve) {
2196 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002197 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002198 }
robertphillipsc506e302014-09-16 09:43:31 -07002199 fLastVec = vec;
2200 break;
2201 case kInvalid_DirChange:
2202 SkFAIL("Use of invalid direction change flag");
2203 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002204 }
2205 }
2206
caryclarkb4216502015-03-02 13:02:34 -08002207 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002208 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002209 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002210 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2211 // value with the current vec is deemed to be of a significant value.
2212 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002213 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002214 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002215 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002216 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002217 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002218 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002219 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002220};
2221
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002222SkPath::Convexity SkPath::internalGetConvexity() const {
2223 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002224 SkPoint pts[4];
2225 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002226 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002227
2228 int contourCount = 0;
2229 int count;
2230 Convexicator state;
2231
caryclarkd3d1a982014-12-08 04:57:38 -08002232 if (!isFinite()) {
2233 return kUnknown_Convexity;
2234 }
reed@google.com04863fa2011-05-15 04:08:24 +00002235 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2236 switch (verb) {
2237 case kMove_Verb:
2238 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002239 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002240 return kConcave_Convexity;
2241 }
2242 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002243 // fall through
2244 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002245 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002246 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002247 break;
caryclark5ccef572015-03-02 10:07:56 -08002248 case kQuad_Verb:
2249 // fall through
2250 case kConic_Verb:
2251 // fall through
2252 case kCubic_Verb:
2253 count = 2 + (kCubic_Verb == verb);
2254 // As an additional enhancement, this could set curve true only
2255 // if the curve is nonlinear
2256 state.setCurve(true);
2257 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002258 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002259 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002260 state.close();
2261 count = 0;
2262 break;
2263 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002264 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002265 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 return kConcave_Convexity;
2267 }
2268
2269 for (int i = 1; i <= count; i++) {
2270 state.addPt(pts[i]);
2271 }
2272 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002273 if (!state.isFinite()) {
2274 return kUnknown_Convexity;
2275 }
reed@google.com04863fa2011-05-15 04:08:24 +00002276 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002277 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002278 return kConcave_Convexity;
2279 }
2280 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002281 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002282 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2283 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002284 }
2285 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002286}
reed@google.com69a99432012-01-10 18:00:10 +00002287
2288///////////////////////////////////////////////////////////////////////////////
2289
2290class ContourIter {
2291public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002292 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002293
2294 bool done() const { return fDone; }
2295 // if !done() then these may be called
2296 int count() const { return fCurrPtCount; }
2297 const SkPoint* pts() const { return fCurrPt; }
2298 void next();
2299
2300private:
2301 int fCurrPtCount;
2302 const SkPoint* fCurrPt;
2303 const uint8_t* fCurrVerb;
2304 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002305 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002306 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002307 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002308};
2309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002310ContourIter::ContourIter(const SkPathRef& pathRef) {
2311 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002312 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002313 fCurrPt = pathRef.points();
2314 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002315 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002316 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002317 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002318 this->next();
2319}
2320
2321void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002322 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002323 fDone = true;
2324 }
2325 if (fDone) {
2326 return;
2327 }
2328
2329 // skip pts of prev contour
2330 fCurrPt += fCurrPtCount;
2331
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002332 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002333 int ptCount = 1; // moveTo
2334 const uint8_t* verbs = fCurrVerb;
2335
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002336 for (--verbs; verbs > fStopVerbs; --verbs) {
2337 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002338 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002339 goto CONTOUR_END;
2340 case SkPath::kLine_Verb:
2341 ptCount += 1;
2342 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002343 case SkPath::kConic_Verb:
2344 fCurrConicWeight += 1;
2345 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002346 case SkPath::kQuad_Verb:
2347 ptCount += 2;
2348 break;
2349 case SkPath::kCubic_Verb:
2350 ptCount += 3;
2351 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002352 case SkPath::kClose_Verb:
2353 break;
2354 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002355 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002356 break;
2357 }
2358 }
2359CONTOUR_END:
2360 fCurrPtCount = ptCount;
2361 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002362 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002363}
2364
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002365// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002366static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002367 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2368 // We may get 0 when the above subtracts underflow. We expect this to be
2369 // very rare and lazily promote to double.
2370 if (0 == cross) {
2371 double p0x = SkScalarToDouble(p0.fX);
2372 double p0y = SkScalarToDouble(p0.fY);
2373
2374 double p1x = SkScalarToDouble(p1.fX);
2375 double p1y = SkScalarToDouble(p1.fY);
2376
2377 double p2x = SkScalarToDouble(p2.fX);
2378 double p2y = SkScalarToDouble(p2.fY);
2379
2380 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2381 (p1y - p0y) * (p2x - p0x));
2382
2383 }
2384 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002385}
2386
reed@google.comc1ea60a2012-01-31 15:15:36 +00002387// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002388static int find_max_y(const SkPoint pts[], int count) {
2389 SkASSERT(count > 0);
2390 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002391 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002392 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002393 SkScalar y = pts[i].fY;
2394 if (y > max) {
2395 max = y;
2396 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002397 }
2398 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002399 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002400}
2401
reed@google.comcabaf1d2012-01-11 21:03:05 +00002402static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2403 int i = index;
2404 for (;;) {
2405 i = (i + inc) % n;
2406 if (i == index) { // we wrapped around, so abort
2407 break;
2408 }
2409 if (pts[index] != pts[i]) { // found a different point, success!
2410 break;
2411 }
2412 }
2413 return i;
2414}
2415
reed@google.comc1ea60a2012-01-31 15:15:36 +00002416/**
2417 * Starting at index, and moving forward (incrementing), find the xmin and
2418 * xmax of the contiguous points that have the same Y.
2419 */
2420static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2421 int* maxIndexPtr) {
2422 const SkScalar y = pts[index].fY;
2423 SkScalar min = pts[index].fX;
2424 SkScalar max = min;
2425 int minIndex = index;
2426 int maxIndex = index;
2427 for (int i = index + 1; i < n; ++i) {
2428 if (pts[i].fY != y) {
2429 break;
2430 }
2431 SkScalar x = pts[i].fX;
2432 if (x < min) {
2433 min = x;
2434 minIndex = i;
2435 } else if (x > max) {
2436 max = x;
2437 maxIndex = i;
2438 }
2439 }
2440 *maxIndexPtr = maxIndex;
2441 return minIndex;
2442}
2443
reed026beb52015-06-10 14:23:15 -07002444static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2445 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002446}
2447
reed@google.comac8543f2012-01-30 20:51:25 +00002448/*
2449 * We loop through all contours, and keep the computed cross-product of the
2450 * contour that contained the global y-max. If we just look at the first
2451 * contour, we may find one that is wound the opposite way (correctly) since
2452 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2453 * that is outer most (or at least has the global y-max) before we can consider
2454 * its cross product.
2455 */
reed026beb52015-06-10 14:23:15 -07002456bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
2457 if (kUnknown_FirstDirection != path.fFirstDirection) {
2458 *dir = static_cast<FirstDirection>(path.fFirstDirection);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002459 return true;
2460 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002461
2462 // don't want to pay the cost for computing this if it
2463 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002464 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2465 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
2466 *dir = static_cast<FirstDirection>(path.fFirstDirection);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002467 return false;
2468 }
reed@google.com69a99432012-01-10 18:00:10 +00002469
reed026beb52015-06-10 14:23:15 -07002470 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002471
reed@google.comac8543f2012-01-30 20:51:25 +00002472 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002473 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002474 SkScalar ymaxCross = 0;
2475
reed@google.com69a99432012-01-10 18:00:10 +00002476 for (; !iter.done(); iter.next()) {
2477 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002478 if (n < 3) {
2479 continue;
2480 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002481
reed@google.comcabaf1d2012-01-11 21:03:05 +00002482 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002483 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002484 int index = find_max_y(pts, n);
2485 if (pts[index].fY < ymax) {
2486 continue;
2487 }
2488
2489 // If there is more than 1 distinct point at the y-max, we take the
2490 // x-min and x-max of them and just subtract to compute the dir.
2491 if (pts[(index + 1) % n].fY == pts[index].fY) {
2492 int maxIndex;
2493 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2494 if (minIndex == maxIndex) {
2495 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002496 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002497 SkASSERT(pts[minIndex].fY == pts[index].fY);
2498 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2499 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2500 // we just subtract the indices, and let that auto-convert to
2501 // SkScalar, since we just want - or + to signal the direction.
2502 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002503 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002504 TRY_CROSSPROD:
2505 // Find a next and prev index to use for the cross-product test,
2506 // but we try to find pts that form non-zero vectors from pts[index]
2507 //
2508 // Its possible that we can't find two non-degenerate vectors, so
2509 // we have to guard our search (e.g. all the pts could be in the
2510 // same place).
2511
2512 // we pass n - 1 instead of -1 so we don't foul up % operator by
2513 // passing it a negative LH argument.
2514 int prev = find_diff_pt(pts, index, n, n - 1);
2515 if (prev == index) {
2516 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002517 continue;
2518 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002519 int next = find_diff_pt(pts, index, n, 1);
2520 SkASSERT(next != index);
2521 cross = cross_prod(pts[prev], pts[index], pts[next]);
2522 // if we get a zero and the points are horizontal, then we look at the spread in
2523 // x-direction. We really should continue to walk away from the degeneracy until
2524 // there is a divergence.
2525 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2526 // construct the subtract so we get the correct Direction below
2527 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002528 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002529 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002530
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002531 if (cross) {
2532 // record our best guess so far
2533 ymax = pts[index].fY;
2534 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002535 }
2536 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002537 if (ymaxCross) {
2538 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002539 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002540 return true;
2541 } else {
2542 return false;
2543 }
reed@google.comac8543f2012-01-30 20:51:25 +00002544}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002545
2546///////////////////////////////////////////////////////////////////////////////
2547
2548static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2549 SkScalar D, SkScalar t) {
2550 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2551}
2552
2553static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2554 SkScalar t) {
2555 SkScalar A = c3 + 3*(c1 - c2) - c0;
2556 SkScalar B = 3*(c2 - c1 - c1 + c0);
2557 SkScalar C = 3*(c1 - c0);
2558 SkScalar D = c0;
2559 return eval_cubic_coeff(A, B, C, D, t);
2560}
2561
2562/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2563 t value such that cubic(t) = target
2564 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002565static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002566 SkScalar target, SkScalar* t) {
2567 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2568 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002569
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002570 SkScalar D = c0 - target;
2571 SkScalar A = c3 + 3*(c1 - c2) - c0;
2572 SkScalar B = 3*(c2 - c1 - c1 + c0);
2573 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002574
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002575 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2576 SkScalar minT = 0;
2577 SkScalar maxT = SK_Scalar1;
2578 SkScalar mid;
2579 int i;
2580 for (i = 0; i < 16; i++) {
2581 mid = SkScalarAve(minT, maxT);
2582 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2583 if (delta < 0) {
2584 minT = mid;
2585 delta = -delta;
2586 } else {
2587 maxT = mid;
2588 }
2589 if (delta < TOLERANCE) {
2590 break;
2591 }
2592 }
2593 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002594}
2595
2596template <size_t N> static void find_minmax(const SkPoint pts[],
2597 SkScalar* minPtr, SkScalar* maxPtr) {
2598 SkScalar min, max;
2599 min = max = pts[0].fX;
2600 for (size_t i = 1; i < N; ++i) {
2601 min = SkMinScalar(min, pts[i].fX);
2602 max = SkMaxScalar(max, pts[i].fX);
2603 }
2604 *minPtr = min;
2605 *maxPtr = max;
2606}
2607
2608static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2609 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002610
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002611 int dir = 1;
2612 if (pts[0].fY > pts[3].fY) {
2613 storage[0] = pts[3];
2614 storage[1] = pts[2];
2615 storage[2] = pts[1];
2616 storage[3] = pts[0];
2617 pts = storage;
2618 dir = -1;
2619 }
2620 if (y < pts[0].fY || y >= pts[3].fY) {
2621 return 0;
2622 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002623
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002624 // quickreject or quickaccept
2625 SkScalar min, max;
2626 find_minmax<4>(pts, &min, &max);
2627 if (x < min) {
2628 return 0;
2629 }
2630 if (x > max) {
2631 return dir;
2632 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002633
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002634 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002635 SkScalar t;
2636 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2637 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 +00002638 return xt < x ? dir : 0;
2639}
2640
2641static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2642 SkPoint dst[10];
2643 int n = SkChopCubicAtYExtrema(pts, dst);
2644 int w = 0;
2645 for (int i = 0; i <= n; ++i) {
2646 w += winding_mono_cubic(&dst[i * 3], x, y);
2647 }
2648 return w;
2649}
2650
2651static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2652 SkScalar y0 = pts[0].fY;
2653 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002654
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002655 int dir = 1;
2656 if (y0 > y2) {
2657 SkTSwap(y0, y2);
2658 dir = -1;
2659 }
2660 if (y < y0 || y >= y2) {
2661 return 0;
2662 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002663
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002664 // bounds check on X (not required. is it faster?)
2665#if 0
2666 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2667 return 0;
2668 }
2669#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002670
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002671 SkScalar roots[2];
2672 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2673 2 * (pts[1].fY - pts[0].fY),
2674 pts[0].fY - y,
2675 roots);
2676 SkASSERT(n <= 1);
2677 SkScalar xt;
2678 if (0 == n) {
2679 SkScalar mid = SkScalarAve(y0, y2);
2680 // Need [0] and [2] if dir == 1
2681 // and [2] and [0] if dir == -1
2682 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2683 } else {
2684 SkScalar t = roots[0];
2685 SkScalar C = pts[0].fX;
2686 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2687 SkScalar B = 2 * (pts[1].fX - C);
2688 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2689 }
2690 return xt < x ? dir : 0;
2691}
2692
2693static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2694 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2695 if (y0 == y1) {
2696 return true;
2697 }
2698 if (y0 < y1) {
2699 return y1 <= y2;
2700 } else {
2701 return y1 >= y2;
2702 }
2703}
2704
2705static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2706 SkPoint dst[5];
2707 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002708
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002709 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2710 n = SkChopQuadAtYExtrema(pts, dst);
2711 pts = dst;
2712 }
2713 int w = winding_mono_quad(pts, x, y);
2714 if (n > 0) {
2715 w += winding_mono_quad(&pts[2], x, y);
2716 }
2717 return w;
2718}
2719
2720static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2721 SkScalar x0 = pts[0].fX;
2722 SkScalar y0 = pts[0].fY;
2723 SkScalar x1 = pts[1].fX;
2724 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002726 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 int dir = 1;
2729 if (y0 > y1) {
2730 SkTSwap(y0, y1);
2731 dir = -1;
2732 }
2733 if (y < y0 || y >= y1) {
2734 return 0;
2735 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002736
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002737 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2738 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002739
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002740 if (SkScalarSignAsInt(cross) == dir) {
2741 dir = 0;
2742 }
2743 return dir;
2744}
2745
reed@google.com4db592c2013-10-30 17:39:43 +00002746static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2747 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2748}
2749
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002750bool SkPath::contains(SkScalar x, SkScalar y) const {
2751 bool isInverse = this->isInverseFillType();
2752 if (this->isEmpty()) {
2753 return isInverse;
2754 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002755
reed@google.com4db592c2013-10-30 17:39:43 +00002756 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757 return isInverse;
2758 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 SkPath::Iter iter(*this, true);
2761 bool done = false;
2762 int w = 0;
2763 do {
2764 SkPoint pts[4];
2765 switch (iter.next(pts, false)) {
2766 case SkPath::kMove_Verb:
2767 case SkPath::kClose_Verb:
2768 break;
2769 case SkPath::kLine_Verb:
2770 w += winding_line(pts, x, y);
2771 break;
2772 case SkPath::kQuad_Verb:
2773 w += winding_quad(pts, x, y);
2774 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002775 case SkPath::kConic_Verb:
2776 SkASSERT(0);
2777 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 case SkPath::kCubic_Verb:
2779 w += winding_cubic(pts, x, y);
2780 break;
2781 case SkPath::kDone_Verb:
2782 done = true;
2783 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002784 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002786
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787 switch (this->getFillType()) {
2788 case SkPath::kEvenOdd_FillType:
2789 case SkPath::kInverseEvenOdd_FillType:
2790 w &= 1;
2791 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002792 default:
2793 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794 }
2795 return SkToBool(w);
2796}