blob: a08abbd14b6bda1e1d8716b6f78652b465d44e98 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
41 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
42 }
43
44 ~SkAutoDisableDirectionCheck() {
45 fPath->fDirection = fSaved;
46 }
47
48private:
49 SkPath* fPath;
50 SkPath::Direction fSaved;
51};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000129 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000131 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132#endif
133{
134 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700135 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136}
137
138void SkPath::resetFields() {
139 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000140 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000142 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000143 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000144
145 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
146 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000147}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000150 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000151 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000152#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000153 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000154#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156}
157
158SkPath::~SkPath() {
159 SkDEBUGCODE(this->validate();)
160}
161
bungeman@google.coma5809a32013-06-21 15:13:34 +0000162SkPath& SkPath::operator=(const SkPath& that) {
163 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 if (this != &that) {
166 fPathRef.reset(SkRef(that.fPathRef.get()));
167 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000168#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000169 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000170#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 }
172 SkDEBUGCODE(this->validate();)
173 return *this;
174}
175
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176void SkPath::copyFields(const SkPath& that) {
177 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000178 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000180 fConvexity = that.fConvexity;
181 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183}
184
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000185bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000186 // note: don't need to look at isConvex or bounds, since just comparing the
187 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000188 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000189 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000190}
191
bungeman@google.coma5809a32013-06-21 15:13:34 +0000192void SkPath::swap(SkPath& that) {
193 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 if (this != &that) {
196 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000197 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
200 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700201 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000202#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000203 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
204#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 }
206}
207
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000208static inline bool check_edge_against_rect(const SkPoint& p0,
209 const SkPoint& p1,
210 const SkRect& rect,
211 SkPath::Direction dir) {
212 const SkPoint* edgeBegin;
213 SkVector v;
214 if (SkPath::kCW_Direction == dir) {
215 v = p1 - p0;
216 edgeBegin = &p0;
217 } else {
218 v = p0 - p1;
219 edgeBegin = &p1;
220 }
221 if (v.fX || v.fY) {
222 // check the cross product of v with the vec from edgeBegin to each rect corner
223 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
224 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
225 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
226 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
227 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
228 return false;
229 }
230 }
231 return true;
232}
233
234bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
235 // This only handles non-degenerate convex paths currently.
236 if (kConvex_Convexity != this->getConvexity()) {
237 return false;
238 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000239
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240 Direction direction;
241 if (!this->cheapComputeDirection(&direction)) {
242 return false;
243 }
244
245 SkPoint firstPt;
246 SkPoint prevPt;
247 RawIter iter(*this);
248 SkPath::Verb verb;
249 SkPoint pts[4];
250 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000251 SkDEBUGCODE(int segmentCount = 0;)
252 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253
254 while ((verb = iter.next(pts)) != kDone_Verb) {
255 int nextPt = -1;
256 switch (verb) {
257 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000258 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 SkDEBUGCODE(++moveCnt);
260 firstPt = prevPt = pts[0];
261 break;
262 case kLine_Verb:
263 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000264 SkASSERT(moveCnt && !closeCount);
265 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266 break;
267 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000268 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000269 SkASSERT(moveCnt && !closeCount);
270 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000271 nextPt = 2;
272 break;
273 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000274 SkASSERT(moveCnt && !closeCount);
275 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000276 nextPt = 3;
277 break;
278 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000279 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 break;
281 default:
282 SkDEBUGFAIL("unknown verb");
283 }
284 if (-1 != nextPt) {
285 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
286 return false;
287 }
288 prevPt = pts[nextPt];
289 }
290 }
291
292 return check_edge_against_rect(prevPt, firstPt, rect, direction);
293}
294
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000295uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000296 uint32_t genID = fPathRef->genID();
297#ifdef SK_BUILD_FOR_ANDROID
298 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
299 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
300#endif
301 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000302}
djsollen@google.come63793a2012-03-21 15:39:03 +0000303
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000304#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000305const SkPath* SkPath::getSourcePath() const {
306 return fSourcePath;
307}
308
309void SkPath::setSourcePath(const SkPath* path) {
310 fSourcePath = path;
311}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000312#endif
313
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314void SkPath::reset() {
315 SkDEBUGCODE(this->validate();)
316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000318 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319}
320
321void SkPath::rewind() {
322 SkDEBUGCODE(this->validate();)
323
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000324 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000325 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326}
327
reed@google.com7e6c4d12012-05-10 14:05:43 +0000328bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000329 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000330
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000331 if (2 == verbCount) {
332 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
333 if (kLine_Verb == fPathRef->atVerb(1)) {
334 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000335 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000336 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000337 line[0] = pts[0];
338 line[1] = pts[1];
339 }
340 return true;
341 }
342 }
343 return false;
344}
345
caryclark@google.comf1316942011-07-26 19:54:45 +0000346/*
347 Determines if path is a rect by keeping track of changes in direction
348 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000349
caryclark@google.comf1316942011-07-26 19:54:45 +0000350 The direction is computed such that:
351 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000352 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000353 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000354 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000355
caryclark@google.comf1316942011-07-26 19:54:45 +0000356A rectangle cycles up/right/down/left or up/left/down/right.
357
358The test fails if:
359 The path is closed, and followed by a line.
360 A second move creates a new endpoint.
361 A diagonal line is parsed.
362 There's more than four changes of direction.
363 There's a discontinuity on the line (e.g., a move in the middle)
364 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000365 The path contains a quadratic or cubic.
366 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000367 *The rectangle doesn't complete a cycle.
368 *The final point isn't equal to the first point.
369
370 *These last two conditions we relax if we have a 3-edge path that would
371 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000372
caryclark@google.comf1316942011-07-26 19:54:45 +0000373It's OK if the path has:
374 Several colinear line segments composing a rectangle side.
375 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000376
caryclark@google.comf1316942011-07-26 19:54:45 +0000377The direction takes advantage of the corners found since opposite sides
378must travel in opposite directions.
379
380FIXME: Allow colinear quads and cubics to be treated like lines.
381FIXME: If the API passes fill-only, return true if the filled stroke
382 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000383
384 first,last,next direction state-machine:
385 0x1 is set if the segment is horizontal
386 0x2 is set if the segment is moving to the right or down
387 thus:
388 two directions are opposites iff (dirA ^ dirB) == 0x2
389 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000390
caryclark@google.comf1316942011-07-26 19:54:45 +0000391 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000392static int rect_make_dir(SkScalar dx, SkScalar dy) {
393 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
394}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000395bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
396 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000397 int corners = 0;
398 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000399 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000400 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000401 first.set(0, 0);
402 last.set(0, 0);
403 int firstDirection = 0;
404 int lastDirection = 0;
405 int nextDirection = 0;
406 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000408 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
410 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000412 savePts = pts;
413 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 autoClose = true;
415 case kLine_Verb: {
416 SkScalar left = last.fX;
417 SkScalar top = last.fY;
418 SkScalar right = pts->fX;
419 SkScalar bottom = pts->fY;
420 ++pts;
421 if (left != right && top != bottom) {
422 return false; // diagonal
423 }
424 if (left == right && top == bottom) {
425 break; // single point on side OK
426 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000427 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 if (0 == corners) {
429 firstDirection = nextDirection;
430 first = last;
431 last = pts[-1];
432 corners = 1;
433 closedOrMoved = false;
434 break;
435 }
436 if (closedOrMoved) {
437 return false; // closed followed by a line
438 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000439 if (autoClose && nextDirection == firstDirection) {
440 break; // colinear with first
441 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 closedOrMoved = autoClose;
443 if (lastDirection != nextDirection) {
444 if (++corners > 4) {
445 return false; // too many direction changes
446 }
447 }
448 last = pts[-1];
449 if (lastDirection == nextDirection) {
450 break; // colinear segment
451 }
452 // Possible values for corners are 2, 3, and 4.
453 // When corners == 3, nextDirection opposes firstDirection.
454 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000455 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
457 if ((directionCycle ^ turn) != nextDirection) {
458 return false; // direction didn't follow cycle
459 }
460 break;
461 }
462 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000463 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 case kCubic_Verb:
465 return false; // quadratic, cubic not allowed
466 case kMove_Verb:
467 last = *pts++;
468 closedOrMoved = true;
469 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000470 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000471 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000472 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000474 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 lastDirection = nextDirection;
476 }
477 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000478 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000479 if (!result) {
480 // check if we are just an incomplete rectangle, in which case we can
481 // return true, but not claim to be closed.
482 // e.g.
483 // 3 sided rectangle
484 // 4 sided but the last edge is not long enough to reach the start
485 //
486 SkScalar closeX = first.x() - last.x();
487 SkScalar closeY = first.y() - last.y();
488 if (closeX && closeY) {
489 return false; // we're diagonal, abort (can we ever reach this?)
490 }
491 int closeDirection = rect_make_dir(closeX, closeY);
492 // make sure the close-segment doesn't double-back on itself
493 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
494 result = true;
495 autoClose = false; // we are not closed
496 }
497 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000498 if (savePts) {
499 *ptsPtr = savePts;
500 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000501 if (result && isClosed) {
502 *isClosed = autoClose;
503 }
504 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000505 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000506 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 return result;
508}
509
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000510SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
511 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000512 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
513 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000514 bool isClosed = false;
515 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
516}
517
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000519 SkDEBUGCODE(this->validate();)
520 int currVerb = 0;
521 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000522 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
523 if (result && rect) {
524 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000526 return result;
527}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000528
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000529bool SkPath::isRect(bool* isClosed, Direction* direction) const {
530 SkDEBUGCODE(this->validate();)
531 int currVerb = 0;
532 const SkPoint* pts = fPathRef->points();
533 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000534}
535
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000537 SkDEBUGCODE(this->validate();)
538 int currVerb = 0;
539 const SkPoint* pts = fPathRef->points();
540 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000541 Direction testDirs[2];
542 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000543 return false;
544 }
545 const SkPoint* last = pts;
546 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000547 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000548 testRects[0].set(first, SkToS32(last - first));
549 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 if (testRects[0].contains(testRects[1])) {
551 if (rects) {
552 rects[0] = testRects[0];
553 rects[1] = testRects[1];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[0];
557 dirs[1] = testDirs[1];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 if (testRects[1].contains(testRects[0])) {
562 if (rects) {
563 rects[0] = testRects[1];
564 rects[1] = testRects[0];
565 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000566 if (dirs) {
567 dirs[0] = testDirs[1];
568 dirs[1] = testDirs[0];
569 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 return true;
571 }
572 }
573 return false;
574}
575
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576int SkPath::countPoints() const {
577 return fPathRef->countPoints();
578}
579
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000580int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 SkDEBUGCODE(this->validate();)
582
583 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000584 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585 int count = SkMin32(max, fPathRef->countPoints());
586 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
587 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588}
589
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000591 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
592 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000593 }
594 return SkPoint::Make(0, 0);
595}
596
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000597int SkPath::countVerbs() const {
598 return fPathRef->countVerbs();
599}
600
601static inline void copy_verbs_reverse(uint8_t* inorderDst,
602 const uint8_t* reversedSrc,
603 int count) {
604 for (int i = 0; i < count; ++i) {
605 inorderDst[i] = reversedSrc[~i];
606 }
607}
608
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000609int SkPath::getVerbs(uint8_t dst[], int max) const {
610 SkDEBUGCODE(this->validate();)
611
612 SkASSERT(max >= 0);
613 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 int count = SkMin32(max, fPathRef->countVerbs());
615 copy_verbs_reverse(dst, fPathRef->verbs(), count);
616 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617}
618
reed@google.com294dd7b2011-10-11 11:58:32 +0000619bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 SkDEBUGCODE(this->validate();)
621
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000623 if (count > 0) {
624 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000627 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000629 if (lastPt) {
630 lastPt->set(0, 0);
631 }
632 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633}
634
635void SkPath::setLastPt(SkScalar x, SkScalar y) {
636 SkDEBUGCODE(this->validate();)
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 if (count == 0) {
640 this->moveTo(x, y);
641 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642 SkPathRef::Editor ed(&fPathRef);
643 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 }
645}
646
reed@google.com04863fa2011-05-15 04:08:24 +0000647void SkPath::setConvexity(Convexity c) {
648 if (fConvexity != c) {
649 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000650 }
651}
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653//////////////////////////////////////////////////////////////////////////////
654// Construction methods
655
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000656#define DIRTY_AFTER_EDIT \
657 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000658 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000659 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000660 } while (0)
661
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662void SkPath::incReserve(U16CPU inc) {
663 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 SkDEBUGCODE(this->validate();)
666}
667
668void SkPath::moveTo(SkScalar x, SkScalar y) {
669 SkDEBUGCODE(this->validate();)
670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000673 // remember our index
674 fLastMoveToIndex = fPathRef->countPoints();
675
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700677
678 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679}
680
681void SkPath::rMoveTo(SkScalar x, SkScalar y) {
682 SkPoint pt;
683 this->getLastPt(&pt);
684 this->moveTo(pt.fX + x, pt.fY + y);
685}
686
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000687void SkPath::injectMoveToIfNeeded() {
688 if (fLastMoveToIndex < 0) {
689 SkScalar x, y;
690 if (fPathRef->countVerbs() == 0) {
691 x = y = 0;
692 } else {
693 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
694 x = pt.fX;
695 y = pt.fY;
696 }
697 this->moveTo(x, y);
698 }
699}
700
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701void SkPath::lineTo(SkScalar x, SkScalar y) {
702 SkDEBUGCODE(this->validate();)
703
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000704 this->injectMoveToIfNeeded();
705
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000706 SkPathRef::Editor ed(&fPathRef);
707 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708
reed@google.comb54455e2011-05-16 14:16:04 +0000709 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710}
711
712void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000713 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 SkPoint pt;
715 this->getLastPt(&pt);
716 this->lineTo(pt.fX + x, pt.fY + y);
717}
718
719void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
720 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000721
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000722 this->injectMoveToIfNeeded();
723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000724 SkPathRef::Editor ed(&fPathRef);
725 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 pts[0].set(x1, y1);
727 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000728
reed@google.comb54455e2011-05-16 14:16:04 +0000729 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730}
731
732void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000733 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 SkPoint pt;
735 this->getLastPt(&pt);
736 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
737}
738
reed@google.com277c3f82013-05-31 15:17:50 +0000739void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
740 SkScalar w) {
741 // check for <= 0 or NaN with this test
742 if (!(w > 0)) {
743 this->lineTo(x2, y2);
744 } else if (!SkScalarIsFinite(w)) {
745 this->lineTo(x1, y1);
746 this->lineTo(x2, y2);
747 } else if (SK_Scalar1 == w) {
748 this->quadTo(x1, y1, x2, y2);
749 } else {
750 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752 this->injectMoveToIfNeeded();
753
reed@google.com277c3f82013-05-31 15:17:50 +0000754 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000755 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000756 pts[0].set(x1, y1);
757 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000758
reed@google.com277c3f82013-05-31 15:17:50 +0000759 DIRTY_AFTER_EDIT;
760 }
761}
762
763void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
764 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000765 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000766 SkPoint pt;
767 this->getLastPt(&pt);
768 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
769}
770
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
772 SkScalar x3, SkScalar y3) {
773 SkDEBUGCODE(this->validate();)
774
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000775 this->injectMoveToIfNeeded();
776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 SkPathRef::Editor ed(&fPathRef);
778 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 pts[0].set(x1, y1);
780 pts[1].set(x2, y2);
781 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782
reed@google.comb54455e2011-05-16 14:16:04 +0000783 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784}
785
786void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
787 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000788 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 SkPoint pt;
790 this->getLastPt(&pt);
791 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
792 pt.fX + x3, pt.fY + y3);
793}
794
795void SkPath::close() {
796 SkDEBUGCODE(this->validate();)
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000800 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 case kLine_Verb:
802 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000803 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000805 case kMove_Verb: {
806 SkPathRef::Editor ed(&fPathRef);
807 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000809 }
reed@google.com277c3f82013-05-31 15:17:50 +0000810 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000811 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000812 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000813 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000814 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000815 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 }
817 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000818
819 // signal that we need a moveTo to follow us (unless we're done)
820#if 0
821 if (fLastMoveToIndex >= 0) {
822 fLastMoveToIndex = ~fLastMoveToIndex;
823 }
824#else
825 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
826#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827}
828
829///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000830
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000831static void assert_known_direction(int dir) {
832 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
833}
834
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835void SkPath::addRect(const SkRect& rect, Direction dir) {
836 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
837}
838
839void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
840 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000841 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000842 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
843 SkAutoDisableDirectionCheck addc(this);
844
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
846
847 this->incReserve(5);
848
849 this->moveTo(left, top);
850 if (dir == kCCW_Direction) {
851 this->lineTo(left, bottom);
852 this->lineTo(right, bottom);
853 this->lineTo(right, top);
854 } else {
855 this->lineTo(right, top);
856 this->lineTo(right, bottom);
857 this->lineTo(left, bottom);
858 }
859 this->close();
860}
861
reed@google.com744faba2012-05-29 19:54:52 +0000862void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
863 SkDEBUGCODE(this->validate();)
864 if (count <= 0) {
865 return;
866 }
867
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000868 fLastMoveToIndex = fPathRef->countPoints();
869
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000870 // +close makes room for the extra kClose_Verb
871 SkPathRef::Editor ed(&fPathRef, count+close, count);
872
873 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000874 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000875 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
876 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000877 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000878
reed@google.com744faba2012-05-29 19:54:52 +0000879 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000880 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000881 }
882
reed@google.com744faba2012-05-29 19:54:52 +0000883 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000885}
886
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000887#include "SkGeometry.h"
888
889static int build_arc_points(const SkRect& oval, SkScalar startAngle,
890 SkScalar sweepAngle,
891 SkPoint pts[kSkBuildQuadArcStorage]) {
892
893 if (0 == sweepAngle &&
894 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
895 // Chrome uses this path to move into and out of ovals. If not
896 // treated as a special case the moves can distort the oval's
897 // bounding box (and break the circle special case).
898 pts[0].set(oval.fRight, oval.centerY());
899 return 1;
900 } else if (0 == oval.width() && 0 == oval.height()) {
901 // Chrome will sometimes create 0 radius round rects. Having degenerate
902 // quad segments in the path prevents the path from being recognized as
903 // a rect.
904 // TODO: optimizing the case where only one of width or height is zero
905 // should also be considered. This case, however, doesn't seem to be
906 // as common as the single point case.
907 pts[0].set(oval.fRight, oval.fTop);
908 return 1;
909 }
910
911 SkVector start, stop;
912
913 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
914 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
915 &stop.fX);
916
917 /* If the sweep angle is nearly (but less than) 360, then due to precision
918 loss in radians-conversion and/or sin/cos, we may end up with coincident
919 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
920 of drawing a nearly complete circle (good).
921 e.g. canvas.drawArc(0, 359.99, ...)
922 -vs- canvas.drawArc(0, 359.9, ...)
923 We try to detect this edge case, and tweak the stop vector
924 */
925 if (start == stop) {
926 SkScalar sw = SkScalarAbs(sweepAngle);
927 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
928 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
929 // make a guess at a tiny angle (in radians) to tweak by
930 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
931 // not sure how much will be enough, so we use a loop
932 do {
933 stopRad -= deltaRad;
934 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
935 } while (start == stop);
936 }
937 }
938
939 SkMatrix matrix;
940
941 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
942 matrix.postTranslate(oval.centerX(), oval.centerY());
943
944 return SkBuildQuadArc(start, stop,
945 sweepAngle > 0 ? kCW_SkRotationDirection :
946 kCCW_SkRotationDirection,
947 &matrix, pts);
948}
949
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000950void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000951 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000952 SkRRect rrect;
953 rrect.setRectRadii(rect, (const SkVector*) radii);
954 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955}
956
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000957/* The inline clockwise and counterclockwise round rect quad approximations
958 make it easier to see the symmetry patterns used by add corner quads.
959Clockwise corner value
960 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
961 path->quadTo(rect.fLeft, rect.fTop + offPtY,
962 rect.fLeft + midPtX, rect.fTop + midPtY);
963 path->quadTo(rect.fLeft + offPtX, rect.fTop,
964 rect.fLeft + rx, rect.fTop);
965
966 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
967 path->quadTo(rect.fRight - offPtX, rect.fTop,
968 rect.fRight - midPtX, rect.fTop + midPtY);
969 path->quadTo(rect.fRight, rect.fTop + offPtY,
970 rect.fRight, rect.fTop + ry);
971
972 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
973 path->quadTo(rect.fRight, rect.fBottom - offPtY,
974 rect.fRight - midPtX, rect.fBottom - midPtY);
975 path->quadTo(rect.fRight - offPtX, rect.fBottom,
976 rect.fRight - rx, rect.fBottom);
977
978 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
979 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
980 rect.fLeft + midPtX, rect.fBottom - midPtY);
981 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
982 rect.fLeft, rect.fBottom - ry);
983
984Counterclockwise
985 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
986 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
987 rect.fLeft + midPtX, rect.fBottom - midPtY);
988 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
989 rect.fLeft + rx, rect.fBottom);
990
991 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
992 path->quadTo(rect.fRight - offPtX, rect.fBottom,
993 rect.fRight - midPtX, rect.fBottom - midPtY);
994 path->quadTo(rect.fRight, rect.fBottom - offPtY,
995 rect.fRight, rect.fBottom - ry);
996
997 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
998 path->quadTo(rect.fRight, rect.fTop + offPtY,
999 rect.fRight - midPtX, rect.fTop + midPtY);
1000 path->quadTo(rect.fRight - offPtX, rect.fTop,
1001 rect.fRight - rx, rect.fTop);
1002
1003 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1004 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1005 rect.fLeft + midPtX, rect.fTop + midPtY);
1006 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1007 rect.fLeft, rect.fTop + ry);
1008*/
1009static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1010 SkRRect::Corner corner, SkPath::Direction dir) {
1011 const SkRect& rect = rrect.rect();
1012 const SkVector& radii = rrect.radii(corner);
1013 SkScalar rx = radii.fX;
1014 SkScalar ry = radii.fY;
1015 // The mid point of the quadratic arc approximation is half way between the two
1016 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001017 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1018 SkScalar midPtX = rx * mid;
1019 SkScalar midPtY = ry * mid;
1020 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1021 SkScalar offPtX = rx * control;
1022 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001023 static const int kCornerPts = 5;
1024 SkScalar xOff[kCornerPts];
1025 SkScalar yOff[kCornerPts];
1026
1027 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1028 SkASSERT(dir == SkPath::kCCW_Direction
1029 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1030 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1031 xOff[0] = xOff[1] = 0;
1032 xOff[2] = midPtX;
1033 xOff[3] = offPtX;
1034 xOff[4] = rx;
1035 yOff[0] = ry;
1036 yOff[1] = offPtY;
1037 yOff[2] = midPtY;
1038 yOff[3] = yOff[4] = 0;
1039 } else {
1040 xOff[0] = rx;
1041 xOff[1] = offPtX;
1042 xOff[2] = midPtX;
1043 xOff[3] = xOff[4] = 0;
1044 yOff[0] = yOff[1] = 0;
1045 yOff[2] = midPtY;
1046 yOff[3] = offPtY;
1047 yOff[4] = ry;
1048 }
1049 if ((corner - 1) & 2) {
1050 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1051 for (int i = 0; i < kCornerPts; ++i) {
1052 xOff[i] = rect.fLeft + xOff[i];
1053 }
1054 } else {
1055 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1056 for (int i = 0; i < kCornerPts; ++i) {
1057 xOff[i] = rect.fRight - xOff[i];
1058 }
1059 }
1060 if (corner < SkRRect::kLowerRight_Corner) {
1061 for (int i = 0; i < kCornerPts; ++i) {
1062 yOff[i] = rect.fTop + yOff[i];
1063 }
1064 } else {
1065 for (int i = 0; i < kCornerPts; ++i) {
1066 yOff[i] = rect.fBottom - yOff[i];
1067 }
1068 }
1069
1070 SkPoint lastPt;
1071 SkAssertResult(path->getLastPt(&lastPt));
1072 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1073 path->lineTo(xOff[0], yOff[0]);
1074 }
1075 if (rx || ry) {
1076 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1077 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1078 } else {
1079 path->lineTo(xOff[2], yOff[2]);
1080 path->lineTo(xOff[4], yOff[4]);
1081 }
1082}
1083
reed@google.com4ed0fb72012-12-12 20:48:18 +00001084void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001085 assert_known_direction(dir);
1086
1087 if (rrect.isEmpty()) {
1088 return;
1089 }
1090
reed@google.com4ed0fb72012-12-12 20:48:18 +00001091 const SkRect& bounds = rrect.getBounds();
1092
1093 if (rrect.isRect()) {
1094 this->addRect(bounds, dir);
1095 } else if (rrect.isOval()) {
1096 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001097 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001098 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001100 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001101 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102
1103 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001104 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001105 this->moveTo(bounds.fLeft,
1106 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1107 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1108 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1109 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1110 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001111 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001112 this->moveTo(bounds.fLeft,
1113 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1114 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1115 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1116 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1117 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001118 }
1119 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001120 }
1121}
1122
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001123bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001124 int count = fPathRef->countVerbs();
1125 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1126 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001127 if (*verbs == kLine_Verb ||
1128 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001129 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001130 *verbs == kCubic_Verb) {
1131 return false;
1132 }
1133 ++verbs;
1134 }
1135 return true;
1136}
1137
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001138void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1139 Direction dir) {
1140 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001141
humper@google.com75e3ca12013-04-08 21:44:11 +00001142 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001143 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001144 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001145 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001146 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1147 return;
1148 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001149
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001150 SkRRect rrect;
1151 rrect.setRectXY(rect, rx, ry);
1152 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001153}
1154
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001156 assert_known_direction(dir);
1157
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001158 /* If addOval() is called after previous moveTo(),
1159 this path is still marked as an oval. This is used to
1160 fit into WebKit's calling sequences.
1161 We can't simply check isEmpty() in this case, as additional
1162 moveTo() would mark the path non empty.
1163 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001164 bool isOval = hasOnlyMoveTos();
1165 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001166 fDirection = dir;
1167 } else {
1168 fDirection = kUnknown_Direction;
1169 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001170
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001171 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkAutoPathBoundsUpdate apbu(this, oval);
1174
1175 SkScalar cx = oval.centerX();
1176 SkScalar cy = oval.centerY();
1177 SkScalar rx = SkScalarHalf(oval.width());
1178 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1181 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1182 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1183 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1184
1185 /*
1186 To handle imprecision in computing the center and radii, we revert to
1187 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1188 to ensure that we don't exceed the oval's bounds *ever*, since we want
1189 to use oval for our fast-bounds, rather than have to recompute it.
1190 */
1191 const SkScalar L = oval.fLeft; // cx - rx
1192 const SkScalar T = oval.fTop; // cy - ry
1193 const SkScalar R = oval.fRight; // cx + rx
1194 const SkScalar B = oval.fBottom; // cy + ry
1195
1196 this->incReserve(17); // 8 quads + close
1197 this->moveTo(R, cy);
1198 if (dir == kCCW_Direction) {
1199 this->quadTo( R, cy - sy, cx + mx, cy - my);
1200 this->quadTo(cx + sx, T, cx , T);
1201 this->quadTo(cx - sx, T, cx - mx, cy - my);
1202 this->quadTo( L, cy - sy, L, cy );
1203 this->quadTo( L, cy + sy, cx - mx, cy + my);
1204 this->quadTo(cx - sx, B, cx , B);
1205 this->quadTo(cx + sx, B, cx + mx, cy + my);
1206 this->quadTo( R, cy + sy, R, cy );
1207 } else {
1208 this->quadTo( R, cy + sy, cx + mx, cy + my);
1209 this->quadTo(cx + sx, B, cx , B);
1210 this->quadTo(cx - sx, B, cx - mx, cy + my);
1211 this->quadTo( L, cy + sy, L, cy );
1212 this->quadTo( L, cy - sy, cx - mx, cy - my);
1213 this->quadTo(cx - sx, T, cx , T);
1214 this->quadTo(cx + sx, T, cx + mx, cy - my);
1215 this->quadTo( R, cy - sy, R, cy );
1216 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218
robertphillips@google.com466310d2013-12-03 16:43:54 +00001219 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001220
robertphillips@google.com466310d2013-12-03 16:43:54 +00001221 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001222}
1223
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1225 if (r > 0) {
1226 SkRect rect;
1227 rect.set(x - r, y - r, x + r, y + r);
1228 this->addOval(rect, dir);
1229 }
1230}
1231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1233 bool forceMoveTo) {
1234 if (oval.width() < 0 || oval.height() < 0) {
1235 return;
1236 }
1237
1238 SkPoint pts[kSkBuildQuadArcStorage];
1239 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1240 SkASSERT((count & 1) == 1);
1241
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001242 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 forceMoveTo = true;
1244 }
1245 this->incReserve(count);
1246 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1247 for (int i = 1; i < count; i += 2) {
1248 this->quadTo(pts[i], pts[i+1]);
1249 }
1250}
1251
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001252void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 if (oval.isEmpty() || 0 == sweepAngle) {
1254 return;
1255 }
1256
1257 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1258
1259 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1260 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1261 return;
1262 }
1263
1264 SkPoint pts[kSkBuildQuadArcStorage];
1265 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1266
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001267 SkDEBUGCODE(this->validate();)
1268 SkASSERT(count & 1);
1269
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001270 fLastMoveToIndex = fPathRef->countPoints();
1271
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001272 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1273
1274 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1275 if (count > 1) {
1276 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1277 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001279
1280 DIRTY_AFTER_EDIT;
1281 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282}
1283
1284/*
1285 Need to handle the case when the angle is sharp, and our computed end-points
1286 for the arc go behind pt1 and/or p2...
1287*/
1288void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1289 SkScalar radius) {
1290 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001291
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 // need to know our prev pt so we can construct tangent vectors
1293 {
1294 SkPoint start;
1295 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001296 // Handle degenerate cases by adding a line to the first point and
1297 // bailing out.
1298 if ((x1 == start.fX && y1 == start.fY) ||
1299 (x1 == x2 && y1 == y2) ||
1300 radius == 0) {
1301 this->lineTo(x1, y1);
1302 return;
1303 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 before.setNormalize(x1 - start.fX, y1 - start.fY);
1305 after.setNormalize(x2 - x1, y2 - y1);
1306 }
reed@google.comabf15c12011-01-18 20:35:51 +00001307
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 SkScalar cosh = SkPoint::DotProduct(before, after);
1309 SkScalar sinh = SkPoint::CrossProduct(before, after);
1310
1311 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001312 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 return;
1314 }
reed@google.comabf15c12011-01-18 20:35:51 +00001315
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1317 if (dist < 0) {
1318 dist = -dist;
1319 }
1320
1321 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1322 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1323 SkRotationDirection arcDir;
1324
1325 // now turn before/after into normals
1326 if (sinh > 0) {
1327 before.rotateCCW();
1328 after.rotateCCW();
1329 arcDir = kCW_SkRotationDirection;
1330 } else {
1331 before.rotateCW();
1332 after.rotateCW();
1333 arcDir = kCCW_SkRotationDirection;
1334 }
1335
1336 SkMatrix matrix;
1337 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001338
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 matrix.setScale(radius, radius);
1340 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1341 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001342
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001344
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 this->incReserve(count);
1346 // [xx,yy] == pts[0]
1347 this->lineTo(xx, yy);
1348 for (int i = 1; i < count; i += 2) {
1349 this->quadTo(pts[i], pts[i+1]);
1350 }
1351}
1352
1353///////////////////////////////////////////////////////////////////////////////
1354
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001355void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 SkMatrix matrix;
1357
1358 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001359 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360}
1361
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001362void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001363 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001365 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366 SkPoint pts[4];
1367 Verb verb;
1368
1369 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001370 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 while ((verb = iter.next(pts)) != kDone_Verb) {
1372 switch (verb) {
1373 case kMove_Verb:
1374 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001375 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1376 injectMoveToIfNeeded(); // In case last contour is closed
1377 this->lineTo(pts[0]);
1378 } else {
1379 this->moveTo(pts[0]);
1380 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 break;
1382 case kLine_Verb:
1383 proc(matrix, &pts[1], &pts[1], 1);
1384 this->lineTo(pts[1]);
1385 break;
1386 case kQuad_Verb:
1387 proc(matrix, &pts[1], &pts[1], 2);
1388 this->quadTo(pts[1], pts[2]);
1389 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001390 case kConic_Verb:
1391 proc(matrix, &pts[1], &pts[1], 2);
1392 this->conicTo(pts[1], pts[2], iter.conicWeight());
1393 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 case kCubic_Verb:
1395 proc(matrix, &pts[1], &pts[1], 3);
1396 this->cubicTo(pts[1], pts[2], pts[3]);
1397 break;
1398 case kClose_Verb:
1399 this->close();
1400 break;
1401 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001402 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001404 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 }
1406}
1407
1408///////////////////////////////////////////////////////////////////////////////
1409
reed@google.com277c3f82013-05-31 15:17:50 +00001410static int pts_in_verb(unsigned verb) {
1411 static const uint8_t gPtsInVerb[] = {
1412 1, // kMove
1413 1, // kLine
1414 2, // kQuad
1415 2, // kConic
1416 3, // kCubic
1417 0, // kClose
1418 0 // kDone
1419 };
1420
1421 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1422 return gPtsInVerb[verb];
1423}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425// ignore the last point of the 1st contour
1426void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001427 int i, vcount = path.fPathRef->countVerbs();
1428 // exit early if the path is empty, or just has a moveTo.
1429 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 return;
1431 }
1432
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001433 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001435 const uint8_t* verbs = path.fPathRef->verbs();
1436 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001437 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001439 SkASSERT(verbs[~0] == kMove_Verb);
1440 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001441 unsigned v = verbs[~i];
1442 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 if (n == 0) {
1444 break;
1445 }
1446 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001447 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 }
1449
1450 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001451 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 case kLine_Verb:
1453 this->lineTo(pts[-1].fX, pts[-1].fY);
1454 break;
1455 case kQuad_Verb:
1456 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1457 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001458 case kConic_Verb:
1459 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1460 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 case kCubic_Verb:
1462 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1463 pts[-3].fX, pts[-3].fY);
1464 break;
1465 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001466 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 break;
1468 }
reed@google.com277c3f82013-05-31 15:17:50 +00001469 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 }
1471}
1472
reed@google.com63d73742012-01-10 15:33:12 +00001473void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001474 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 const SkPoint* pts = src.fPathRef->pointsEnd();
1477 // we will iterator through src's verbs backwards
1478 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1479 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001480 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001481
1482 bool needMove = true;
1483 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 while (verbs < verbsEnd) {
1485 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001486 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001487
1488 if (needMove) {
1489 --pts;
1490 this->moveTo(pts->fX, pts->fY);
1491 needMove = false;
1492 }
1493 pts -= n;
1494 switch (v) {
1495 case kMove_Verb:
1496 if (needClose) {
1497 this->close();
1498 needClose = false;
1499 }
1500 needMove = true;
1501 pts += 1; // so we see the point in "if (needMove)" above
1502 break;
1503 case kLine_Verb:
1504 this->lineTo(pts[0]);
1505 break;
1506 case kQuad_Verb:
1507 this->quadTo(pts[1], pts[0]);
1508 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001509 case kConic_Verb:
1510 this->conicTo(pts[1], pts[0], *--conicWeights);
1511 break;
reed@google.com63d73742012-01-10 15:33:12 +00001512 case kCubic_Verb:
1513 this->cubicTo(pts[2], pts[1], pts[0]);
1514 break;
1515 case kClose_Verb:
1516 needClose = true;
1517 break;
1518 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001519 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001520 }
1521 }
1522}
1523
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524///////////////////////////////////////////////////////////////////////////////
1525
1526void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1527 SkMatrix matrix;
1528
1529 matrix.setTranslate(dx, dy);
1530 this->transform(matrix, dst);
1531}
1532
1533#include "SkGeometry.h"
1534
1535static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1536 int level = 2) {
1537 if (--level >= 0) {
1538 SkPoint tmp[5];
1539
1540 SkChopQuadAtHalf(pts, tmp);
1541 subdivide_quad_to(path, &tmp[0], level);
1542 subdivide_quad_to(path, &tmp[2], level);
1543 } else {
1544 path->quadTo(pts[1], pts[2]);
1545 }
1546}
1547
1548static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1549 int level = 2) {
1550 if (--level >= 0) {
1551 SkPoint tmp[7];
1552
1553 SkChopCubicAtHalf(pts, tmp);
1554 subdivide_cubic_to(path, &tmp[0], level);
1555 subdivide_cubic_to(path, &tmp[3], level);
1556 } else {
1557 path->cubicTo(pts[1], pts[2], pts[3]);
1558 }
1559}
1560
1561void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1562 SkDEBUGCODE(this->validate();)
1563 if (dst == NULL) {
1564 dst = (SkPath*)this;
1565 }
1566
tomhudson@google.com8d430182011-06-06 19:11:19 +00001567 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 SkPath tmp;
1569 tmp.fFillType = fFillType;
1570
1571 SkPath::Iter iter(*this, false);
1572 SkPoint pts[4];
1573 SkPath::Verb verb;
1574
reed@google.com4a3b7142012-05-16 17:16:46 +00001575 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 switch (verb) {
1577 case kMove_Verb:
1578 tmp.moveTo(pts[0]);
1579 break;
1580 case kLine_Verb:
1581 tmp.lineTo(pts[1]);
1582 break;
1583 case kQuad_Verb:
1584 subdivide_quad_to(&tmp, pts);
1585 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001586 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001587 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001588 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1589 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 case kCubic_Verb:
1591 subdivide_cubic_to(&tmp, pts);
1592 break;
1593 case kClose_Verb:
1594 tmp.close();
1595 break;
1596 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001597 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 break;
1599 }
1600 }
1601
1602 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001603 SkPathRef::Editor ed(&dst->fPathRef);
1604 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001605 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001607 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1608
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001611 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001612 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001614
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001615 if (kUnknown_Direction == fDirection) {
1616 dst->fDirection = kUnknown_Direction;
1617 } else {
1618 SkScalar det2x2 =
1619 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1620 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1621 if (det2x2 < 0) {
1622 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1623 } else if (det2x2 > 0) {
1624 dst->fDirection = fDirection;
1625 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001626 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001627 dst->fDirection = kUnknown_Direction;
1628 }
1629 }
1630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 SkDEBUGCODE(dst->validate();)
1632 }
1633}
1634
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635///////////////////////////////////////////////////////////////////////////////
1636///////////////////////////////////////////////////////////////////////////////
1637
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001638enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001639 kEmptyContour_SegmentState, // The current contour is empty. We may be
1640 // starting processing or we may have just
1641 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001642 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1643 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1644 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645};
1646
1647SkPath::Iter::Iter() {
1648#ifdef SK_DEBUG
1649 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001650 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001652 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001653 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654#endif
1655 // need to init enough to make next() harmlessly return kDone_Verb
1656 fVerbs = NULL;
1657 fVerbStop = NULL;
1658 fNeedClose = false;
1659}
1660
1661SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1662 this->setPath(path, forceClose);
1663}
1664
1665void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001666 fPts = path.fPathRef->points();
1667 fVerbs = path.fPathRef->verbs();
1668 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001669 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001670 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001671 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 fForceClose = SkToU8(forceClose);
1673 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001674 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675}
1676
1677bool SkPath::Iter::isClosedContour() const {
1678 if (fVerbs == NULL || fVerbs == fVerbStop) {
1679 return false;
1680 }
1681 if (fForceClose) {
1682 return true;
1683 }
1684
1685 const uint8_t* verbs = fVerbs;
1686 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 if (kMove_Verb == *(verbs - 1)) {
1689 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 }
1691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001692 while (verbs > stop) {
1693 // verbs points one beyond the current verb, decrement first.
1694 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 if (kMove_Verb == v) {
1696 break;
1697 }
1698 if (kClose_Verb == v) {
1699 return true;
1700 }
1701 }
1702 return false;
1703}
1704
1705SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001706 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001708 // A special case: if both points are NaN, SkPoint::operation== returns
1709 // false, but the iterator expects that they are treated as the same.
1710 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001711 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1712 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001713 return kClose_Verb;
1714 }
1715
reed@google.com9e25dbf2012-05-15 17:05:38 +00001716 pts[0] = fLastPt;
1717 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718 fLastPt = fMoveTo;
1719 fCloseLine = true;
1720 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001721 } else {
1722 pts[0] = fMoveTo;
1723 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725}
1726
reed@google.com9e25dbf2012-05-15 17:05:38 +00001727const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728 if (fSegmentState == kAfterMove_SegmentState) {
1729 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001731 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001733 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1734 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001735 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737}
1738
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739void SkPath::Iter::consumeDegenerateSegments() {
1740 // We need to step over anything that will not move the current draw point
1741 // forward before the next move is seen
1742 const uint8_t* lastMoveVerb = 0;
1743 const SkPoint* lastMovePt = 0;
1744 SkPoint lastPt = fLastPt;
1745 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001746 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001747 switch (verb) {
1748 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001749 // Keep a record of this most recent move
1750 lastMoveVerb = fVerbs;
1751 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001752 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001753 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001754 fPts++;
1755 break;
1756
1757 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001758 // A close when we are in a segment is always valid except when it
1759 // follows a move which follows a segment.
1760 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761 return;
1762 }
1763 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001765 break;
1766
1767 case kLine_Verb:
1768 if (!IsLineDegenerate(lastPt, fPts[0])) {
1769 if (lastMoveVerb) {
1770 fVerbs = lastMoveVerb;
1771 fPts = lastMovePt;
1772 return;
1773 }
1774 return;
1775 }
1776 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001777 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001778 fPts++;
1779 break;
1780
reed@google.com277c3f82013-05-31 15:17:50 +00001781 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001782 case kQuad_Verb:
1783 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1784 if (lastMoveVerb) {
1785 fVerbs = lastMoveVerb;
1786 fPts = lastMovePt;
1787 return;
1788 }
1789 return;
1790 }
1791 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001794 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001795 break;
1796
1797 case kCubic_Verb:
1798 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1799 if (lastMoveVerb) {
1800 fVerbs = lastMoveVerb;
1801 fPts = lastMovePt;
1802 return;
1803 }
1804 return;
1805 }
1806 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 fPts += 3;
1809 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001810
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001812 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 }
1814 }
1815}
1816
reed@google.com4a3b7142012-05-16 17:16:46 +00001817SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001818 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001821 // Close the curve if requested and if there is some curve to close
1822 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001823 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 return kLine_Verb;
1825 }
1826 fNeedClose = false;
1827 return kClose_Verb;
1828 }
1829 return kDone_Verb;
1830 }
1831
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 // fVerbs is one beyond the current verb, decrement first
1833 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001834 const SkPoint* SK_RESTRICT srcPts = fPts;
1835 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836
1837 switch (verb) {
1838 case kMove_Verb:
1839 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001840 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 verb = this->autoClose(pts);
1842 if (verb == kClose_Verb) {
1843 fNeedClose = false;
1844 }
1845 return (Verb)verb;
1846 }
1847 if (fVerbs == fVerbStop) { // might be a trailing moveto
1848 return kDone_Verb;
1849 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001850 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001851 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001853 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 fNeedClose = fForceClose;
1856 break;
1857 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001858 pts[0] = this->cons_moveTo();
1859 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 fLastPt = srcPts[0];
1861 fCloseLine = false;
1862 srcPts += 1;
1863 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001864 case kConic_Verb:
1865 fConicWeights += 1;
1866 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001868 pts[0] = this->cons_moveTo();
1869 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 fLastPt = srcPts[1];
1871 srcPts += 2;
1872 break;
1873 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001874 pts[0] = this->cons_moveTo();
1875 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 fLastPt = srcPts[2];
1877 srcPts += 3;
1878 break;
1879 case kClose_Verb:
1880 verb = this->autoClose(pts);
1881 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 } else {
1884 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001885 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 break;
1889 }
1890 fPts = srcPts;
1891 return (Verb)verb;
1892}
1893
1894///////////////////////////////////////////////////////////////////////////////
1895
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001896SkPath::RawIter::RawIter() {
1897#ifdef SK_DEBUG
1898 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001899 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001900 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1901#endif
1902 // need to init enough to make next() harmlessly return kDone_Verb
1903 fVerbs = NULL;
1904 fVerbStop = NULL;
1905}
1906
1907SkPath::RawIter::RawIter(const SkPath& path) {
1908 this->setPath(path);
1909}
1910
1911void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001912 fPts = path.fPathRef->points();
1913 fVerbs = path.fPathRef->verbs();
1914 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001915 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001916 fMoveTo.fX = fMoveTo.fY = 0;
1917 fLastPt.fX = fLastPt.fY = 0;
1918}
1919
1920SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001921 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001922 if (fVerbs == fVerbStop) {
1923 return kDone_Verb;
1924 }
1925
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001926 // fVerbs points one beyond next verb so decrement first.
1927 unsigned verb = *(--fVerbs);
1928 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001929
1930 switch (verb) {
1931 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001932 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001933 fMoveTo = srcPts[0];
1934 fLastPt = fMoveTo;
1935 srcPts += 1;
1936 break;
1937 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001938 pts[0] = fLastPt;
1939 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001940 fLastPt = srcPts[0];
1941 srcPts += 1;
1942 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001943 case kConic_Verb:
1944 fConicWeights += 1;
1945 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001946 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001947 pts[0] = fLastPt;
1948 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001949 fLastPt = srcPts[1];
1950 srcPts += 2;
1951 break;
1952 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001953 pts[0] = fLastPt;
1954 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001955 fLastPt = srcPts[2];
1956 srcPts += 3;
1957 break;
1958 case kClose_Verb:
1959 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001960 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001961 break;
1962 }
1963 fPts = srcPts;
1964 return (Verb)verb;
1965}
1966
1967///////////////////////////////////////////////////////////////////////////////
1968
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001970 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001971*/
1972
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001973size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 SkDEBUGCODE(this->validate();)
1975
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001976 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001977 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001978 return SkAlign4(byteCount);
1979 }
1980
1981 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001982
robertphillips@google.com466310d2013-12-03 16:43:54 +00001983 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001984 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001985 (fDirection << kDirection_SerializationShift) |
1986 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001987
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001988 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001989
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001990 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001991
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001992 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001993 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994}
1995
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001996size_t SkPath::readFromMemory(const void* storage, size_t length) {
1997 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001998
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001999 int32_t packed;
2000 if (!buffer.readS32(&packed)) {
2001 return 0;
2002 }
2003
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002004 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2005 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002006 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002007 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002008 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002009
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002010 size_t sizeRead = 0;
2011 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002012 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002013 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002014 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002015 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002016 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002017 // If the buffer is not valid, pathRef should be NULL
2018 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002019 }
2020 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021}
2022
2023///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024
reed@google.com51bbe752013-01-17 22:07:50 +00002025#include "SkString.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002026#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002027
caryclarke9562592014-09-15 09:26:09 -07002028static void append_scalar(SkString* str, SkScalar value, bool dumpAsHex) {
2029 if (dumpAsHex) {
2030 str->appendf("SkBits2Float(0x%08x)", SkFloat2Bits(value));
2031 return;
2032 }
reed@google.com51bbe752013-01-17 22:07:50 +00002033 SkString tmp;
2034 tmp.printf("%g", value);
2035 if (tmp.contains('.')) {
2036 tmp.appendUnichar('f');
2037 }
2038 str->append(tmp);
2039}
2040
2041static void append_params(SkString* str, const char label[], const SkPoint pts[],
caryclarke9562592014-09-15 09:26:09 -07002042 int count, bool dumpAsHex, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002043 str->append(label);
2044 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002045
reed@google.com51bbe752013-01-17 22:07:50 +00002046 const SkScalar* values = &pts[0].fX;
2047 count *= 2;
2048
2049 for (int i = 0; i < count; ++i) {
caryclarke9562592014-09-15 09:26:09 -07002050 append_scalar(str, values[i], dumpAsHex);
reed@google.com51bbe752013-01-17 22:07:50 +00002051 if (i < count - 1) {
2052 str->append(", ");
2053 }
2054 }
reed@google.com277c3f82013-05-31 15:17:50 +00002055 if (conicWeight >= 0) {
2056 str->append(", ");
caryclarke9562592014-09-15 09:26:09 -07002057 append_scalar(str, conicWeight, dumpAsHex);
reed@google.com277c3f82013-05-31 15:17:50 +00002058 }
caryclark08fa28c2014-10-23 13:08:56 -07002059 str->append(");");
2060 if (dumpAsHex) {
2061 str->append(" // ");
2062 for (int i = 0; i < count; ++i) {
2063 append_scalar(str, values[i], false);
2064 if (i < count - 1) {
2065 str->append(", ");
2066 }
2067 }
2068 if (conicWeight >= 0) {
2069 str->append(", ");
2070 append_scalar(str, conicWeight, false);
2071 }
2072 }
2073 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002074}
2075
caryclarke9562592014-09-15 09:26:09 -07002076void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077 Iter iter(*this, forceClose);
2078 SkPoint pts[4];
2079 Verb verb;
2080
caryclark66a5d8b2014-06-24 08:30:15 -07002081 if (!wStream) {
2082 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2083 }
reed@google.com51bbe752013-01-17 22:07:50 +00002084 SkString builder;
2085
reed@google.com4a3b7142012-05-16 17:16:46 +00002086 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 switch (verb) {
2088 case kMove_Verb:
caryclarke9562592014-09-15 09:26:09 -07002089 append_params(&builder, "path.moveTo", &pts[0], 1, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090 break;
2091 case kLine_Verb:
caryclarke9562592014-09-15 09:26:09 -07002092 append_params(&builder, "path.lineTo", &pts[1], 1, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 break;
2094 case kQuad_Verb:
caryclarke9562592014-09-15 09:26:09 -07002095 append_params(&builder, "path.quadTo", &pts[1], 2, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002097 case kConic_Verb:
caryclarke9562592014-09-15 09:26:09 -07002098 append_params(&builder, "path.conicTo", &pts[1], 2, dumpAsHex, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002099 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 case kCubic_Verb:
caryclarke9562592014-09-15 09:26:09 -07002101 append_params(&builder, "path.cubicTo", &pts[1], 3, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 break;
2103 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002104 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 break;
2106 default:
2107 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2108 verb = kDone_Verb; // stop the loop
2109 break;
2110 }
2111 }
caryclark66a5d8b2014-06-24 08:30:15 -07002112 if (wStream) {
2113 wStream->writeText(builder.c_str());
2114 } else {
2115 SkDebugf("%s", builder.c_str());
2116 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117}
2118
reed@android.come522ca52009-11-23 20:10:41 +00002119void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002120 this->dump(NULL, false, false);
2121}
2122
2123void SkPath::dumpHex() const {
2124 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002125}
2126
2127#ifdef SK_DEBUG
2128void SkPath::validate() const {
2129 SkASSERT(this != NULL);
2130 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002131
djsollen@google.com077348c2012-10-22 20:23:32 +00002132#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002133 if (!fBoundsIsDirty) {
2134 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002135
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002136 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002137 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002138
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002139 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002140 // if we're empty, fBounds may be empty but translated, so we can't
2141 // necessarily compare to bounds directly
2142 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2143 // be [2, 2, 2, 2]
2144 SkASSERT(bounds.isEmpty());
2145 SkASSERT(fBounds.isEmpty());
2146 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002147 if (bounds.isEmpty()) {
2148 SkASSERT(fBounds.isEmpty());
2149 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002150 if (!fBounds.isEmpty()) {
2151 SkASSERT(fBounds.contains(bounds));
2152 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002153 }
reed@android.come522ca52009-11-23 20:10:41 +00002154 }
2155 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002156#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002157}
djsollen@google.com077348c2012-10-22 20:23:32 +00002158#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002159
reed@google.com04863fa2011-05-15 04:08:24 +00002160///////////////////////////////////////////////////////////////////////////////
2161
reed@google.com0b7b9822011-05-16 12:29:27 +00002162static int sign(SkScalar x) { return x < 0; }
2163#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002164
robertphillipsc506e302014-09-16 09:43:31 -07002165enum DirChange {
2166 kLeft_DirChange,
2167 kRight_DirChange,
2168 kStraight_DirChange,
2169 kBackwards_DirChange,
2170
2171 kInvalid_DirChange
2172};
2173
2174
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002175static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002176 // The error epsilon was empirically derived; worse case round rects
2177 // with a mid point outset by 2x float epsilon in tests had an error
2178 // of 12.
2179 const int epsilon = 16;
2180 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2181 return false;
2182 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002183 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002184 int aBits = SkFloatAs2sCompliment(compA);
2185 int bBits = SkFloatAs2sCompliment(compB);
2186 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002187}
2188
robertphillipsc506e302014-09-16 09:43:31 -07002189static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2190 const SkVector& lastVec, const SkVector& curVec) {
2191 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2192
2193 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2194 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2195 largest = SkTMax(largest, -smallest);
2196
2197 if (!almost_equal(largest, largest + cross)) {
2198 int sign = SkScalarSignAsInt(cross);
2199 if (sign) {
2200 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2201 }
2202 }
2203
2204 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2205 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2206 lastVec.dot(curVec) < 0.0f) {
2207 return kBackwards_DirChange;
2208 }
2209
2210 return kStraight_DirChange;
2211}
2212
reed@google.com04863fa2011-05-15 04:08:24 +00002213// only valid for a single contour
2214struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002215 Convexicator()
2216 : fPtCount(0)
2217 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002218 , fDirection(SkPath::kUnknown_Direction)
2219 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002220 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002221 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002222 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002223 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002224 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002225 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002226
2227 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002228 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002229 }
2230
2231 SkPath::Convexity getConvexity() const { return fConvexity; }
2232
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002233 /** The direction returned is only valid if the path is determined convex */
2234 SkPath::Direction getDirection() const { return fDirection; }
2235
reed@google.com04863fa2011-05-15 04:08:24 +00002236 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002237 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002238 return;
2239 }
2240
2241 if (0 == fPtCount) {
2242 fCurrPt = pt;
2243 ++fPtCount;
2244 } else {
2245 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002246 SkScalar lengthSqd = vec.lengthSqd();
2247 if (!SkScalarIsFinite(lengthSqd)) {
2248 fIsFinite = false;
2249 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002250 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002251 fCurrPt = pt;
2252 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002253 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002254 } else {
2255 SkASSERT(fPtCount > 2);
2256 this->addVec(vec);
2257 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002258
reed@google.com85b6e392011-05-15 20:25:17 +00002259 int sx = sign(vec.fX);
2260 int sy = sign(vec.fY);
2261 fDx += (sx != fSx);
2262 fDy += (sy != fSy);
2263 fSx = sx;
2264 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002265
reed@google.com85b6e392011-05-15 20:25:17 +00002266 if (fDx > 3 || fDy > 3) {
2267 fConvexity = SkPath::kConcave_Convexity;
2268 }
reed@google.com04863fa2011-05-15 04:08:24 +00002269 }
2270 }
2271 }
2272
2273 void close() {
2274 if (fPtCount > 2) {
2275 this->addVec(fFirstVec);
2276 }
2277 }
2278
caryclarkd3d1a982014-12-08 04:57:38 -08002279 bool isFinite() const {
2280 return fIsFinite;
2281 }
2282
reed@google.com04863fa2011-05-15 04:08:24 +00002283private:
2284 void addVec(const SkVector& vec) {
2285 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002286 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2287 switch (dir) {
2288 case kLeft_DirChange: // fall through
2289 case kRight_DirChange:
2290 if (kInvalid_DirChange == fExpectedDir) {
2291 fExpectedDir = dir;
2292 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2293 : SkPath::kCCW_Direction;
2294 } else if (dir != fExpectedDir) {
2295 fConvexity = SkPath::kConcave_Convexity;
2296 fDirection = SkPath::kUnknown_Direction;
2297 }
2298 fLastVec = vec;
2299 break;
2300 case kStraight_DirChange:
2301 break;
2302 case kBackwards_DirChange:
2303 fLastVec = vec;
2304 break;
2305 case kInvalid_DirChange:
2306 SkFAIL("Use of invalid direction change flag");
2307 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002308 }
2309 }
2310
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002311 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002313 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2314 // value with the current vec is deemed to be of a significant value.
2315 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002316 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002317 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002318 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002319 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002320 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002321 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002322};
2323
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002324SkPath::Convexity SkPath::internalGetConvexity() const {
2325 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002326 SkPoint pts[4];
2327 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002329
2330 int contourCount = 0;
2331 int count;
2332 Convexicator state;
2333
caryclarkd3d1a982014-12-08 04:57:38 -08002334 if (!isFinite()) {
2335 return kUnknown_Convexity;
2336 }
reed@google.com04863fa2011-05-15 04:08:24 +00002337 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2338 switch (verb) {
2339 case kMove_Verb:
2340 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002341 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002342 return kConcave_Convexity;
2343 }
2344 pts[1] = pts[0];
2345 count = 1;
2346 break;
2347 case kLine_Verb: count = 1; break;
2348 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002349 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002350 case kCubic_Verb: count = 3; break;
2351 case kClose_Verb:
2352 state.close();
2353 count = 0;
2354 break;
2355 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002356 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002358 return kConcave_Convexity;
2359 }
2360
2361 for (int i = 1; i <= count; i++) {
2362 state.addPt(pts[i]);
2363 }
2364 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002365 if (!state.isFinite()) {
2366 return kUnknown_Convexity;
2367 }
reed@google.com04863fa2011-05-15 04:08:24 +00002368 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002369 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 return kConcave_Convexity;
2371 }
2372 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002373 fConvexity = state.getConvexity();
2374 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2375 fDirection = state.getDirection();
2376 }
2377 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002378}
reed@google.com69a99432012-01-10 18:00:10 +00002379
2380///////////////////////////////////////////////////////////////////////////////
2381
2382class ContourIter {
2383public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002384 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002385
2386 bool done() const { return fDone; }
2387 // if !done() then these may be called
2388 int count() const { return fCurrPtCount; }
2389 const SkPoint* pts() const { return fCurrPt; }
2390 void next();
2391
2392private:
2393 int fCurrPtCount;
2394 const SkPoint* fCurrPt;
2395 const uint8_t* fCurrVerb;
2396 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002397 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002398 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002399 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002400};
2401
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002402ContourIter::ContourIter(const SkPathRef& pathRef) {
2403 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002404 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002405 fCurrPt = pathRef.points();
2406 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002407 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002408 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002409 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002410 this->next();
2411}
2412
2413void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002414 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002415 fDone = true;
2416 }
2417 if (fDone) {
2418 return;
2419 }
2420
2421 // skip pts of prev contour
2422 fCurrPt += fCurrPtCount;
2423
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002424 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002425 int ptCount = 1; // moveTo
2426 const uint8_t* verbs = fCurrVerb;
2427
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002428 for (--verbs; verbs > fStopVerbs; --verbs) {
2429 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002430 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002431 goto CONTOUR_END;
2432 case SkPath::kLine_Verb:
2433 ptCount += 1;
2434 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002435 case SkPath::kConic_Verb:
2436 fCurrConicWeight += 1;
2437 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002438 case SkPath::kQuad_Verb:
2439 ptCount += 2;
2440 break;
2441 case SkPath::kCubic_Verb:
2442 ptCount += 3;
2443 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002444 case SkPath::kClose_Verb:
2445 break;
2446 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002447 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002448 break;
2449 }
2450 }
2451CONTOUR_END:
2452 fCurrPtCount = ptCount;
2453 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002454 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002455}
2456
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002457// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002458static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002459 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2460 // We may get 0 when the above subtracts underflow. We expect this to be
2461 // very rare and lazily promote to double.
2462 if (0 == cross) {
2463 double p0x = SkScalarToDouble(p0.fX);
2464 double p0y = SkScalarToDouble(p0.fY);
2465
2466 double p1x = SkScalarToDouble(p1.fX);
2467 double p1y = SkScalarToDouble(p1.fY);
2468
2469 double p2x = SkScalarToDouble(p2.fX);
2470 double p2y = SkScalarToDouble(p2.fY);
2471
2472 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2473 (p1y - p0y) * (p2x - p0x));
2474
2475 }
2476 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002477}
2478
reed@google.comc1ea60a2012-01-31 15:15:36 +00002479// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002480static int find_max_y(const SkPoint pts[], int count) {
2481 SkASSERT(count > 0);
2482 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002483 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002484 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002485 SkScalar y = pts[i].fY;
2486 if (y > max) {
2487 max = y;
2488 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002489 }
2490 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002491 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002492}
2493
reed@google.comcabaf1d2012-01-11 21:03:05 +00002494static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2495 int i = index;
2496 for (;;) {
2497 i = (i + inc) % n;
2498 if (i == index) { // we wrapped around, so abort
2499 break;
2500 }
2501 if (pts[index] != pts[i]) { // found a different point, success!
2502 break;
2503 }
2504 }
2505 return i;
2506}
2507
reed@google.comc1ea60a2012-01-31 15:15:36 +00002508/**
2509 * Starting at index, and moving forward (incrementing), find the xmin and
2510 * xmax of the contiguous points that have the same Y.
2511 */
2512static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2513 int* maxIndexPtr) {
2514 const SkScalar y = pts[index].fY;
2515 SkScalar min = pts[index].fX;
2516 SkScalar max = min;
2517 int minIndex = index;
2518 int maxIndex = index;
2519 for (int i = index + 1; i < n; ++i) {
2520 if (pts[i].fY != y) {
2521 break;
2522 }
2523 SkScalar x = pts[i].fX;
2524 if (x < min) {
2525 min = x;
2526 minIndex = i;
2527 } else if (x > max) {
2528 max = x;
2529 maxIndex = i;
2530 }
2531 }
2532 *maxIndexPtr = maxIndex;
2533 return minIndex;
2534}
2535
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002536static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002537 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002538}
2539
reed@google.comac8543f2012-01-30 20:51:25 +00002540/*
2541 * We loop through all contours, and keep the computed cross-product of the
2542 * contour that contained the global y-max. If we just look at the first
2543 * contour, we may find one that is wound the opposite way (correctly) since
2544 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2545 * that is outer most (or at least has the global y-max) before we can consider
2546 * its cross product.
2547 */
reed@google.com69a99432012-01-10 18:00:10 +00002548bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002549 if (kUnknown_Direction != fDirection) {
2550 *dir = static_cast<Direction>(fDirection);
2551 return true;
2552 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002553
2554 // don't want to pay the cost for computing this if it
2555 // is unknown, so we don't call isConvex()
2556 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2557 SkASSERT(kUnknown_Direction == fDirection);
2558 *dir = static_cast<Direction>(fDirection);
2559 return false;
2560 }
reed@google.com69a99432012-01-10 18:00:10 +00002561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002562 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002563
reed@google.comac8543f2012-01-30 20:51:25 +00002564 // initialize with our logical y-min
2565 SkScalar ymax = this->getBounds().fTop;
2566 SkScalar ymaxCross = 0;
2567
reed@google.com69a99432012-01-10 18:00:10 +00002568 for (; !iter.done(); iter.next()) {
2569 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002570 if (n < 3) {
2571 continue;
2572 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002573
reed@google.comcabaf1d2012-01-11 21:03:05 +00002574 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002575 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002576 int index = find_max_y(pts, n);
2577 if (pts[index].fY < ymax) {
2578 continue;
2579 }
2580
2581 // If there is more than 1 distinct point at the y-max, we take the
2582 // x-min and x-max of them and just subtract to compute the dir.
2583 if (pts[(index + 1) % n].fY == pts[index].fY) {
2584 int maxIndex;
2585 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2586 if (minIndex == maxIndex) {
2587 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002588 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002589 SkASSERT(pts[minIndex].fY == pts[index].fY);
2590 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2591 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2592 // we just subtract the indices, and let that auto-convert to
2593 // SkScalar, since we just want - or + to signal the direction.
2594 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002595 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002596 TRY_CROSSPROD:
2597 // Find a next and prev index to use for the cross-product test,
2598 // but we try to find pts that form non-zero vectors from pts[index]
2599 //
2600 // Its possible that we can't find two non-degenerate vectors, so
2601 // we have to guard our search (e.g. all the pts could be in the
2602 // same place).
2603
2604 // we pass n - 1 instead of -1 so we don't foul up % operator by
2605 // passing it a negative LH argument.
2606 int prev = find_diff_pt(pts, index, n, n - 1);
2607 if (prev == index) {
2608 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002609 continue;
2610 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002611 int next = find_diff_pt(pts, index, n, 1);
2612 SkASSERT(next != index);
2613 cross = cross_prod(pts[prev], pts[index], pts[next]);
2614 // if we get a zero and the points are horizontal, then we look at the spread in
2615 // x-direction. We really should continue to walk away from the degeneracy until
2616 // there is a divergence.
2617 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2618 // construct the subtract so we get the correct Direction below
2619 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002620 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002621 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002622
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002623 if (cross) {
2624 // record our best guess so far
2625 ymax = pts[index].fY;
2626 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002627 }
2628 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002629 if (ymaxCross) {
2630 crossToDir(ymaxCross, dir);
2631 fDirection = *dir;
2632 return true;
2633 } else {
2634 return false;
2635 }
reed@google.comac8543f2012-01-30 20:51:25 +00002636}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002637
2638///////////////////////////////////////////////////////////////////////////////
2639
2640static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2641 SkScalar D, SkScalar t) {
2642 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2643}
2644
2645static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2646 SkScalar t) {
2647 SkScalar A = c3 + 3*(c1 - c2) - c0;
2648 SkScalar B = 3*(c2 - c1 - c1 + c0);
2649 SkScalar C = 3*(c1 - c0);
2650 SkScalar D = c0;
2651 return eval_cubic_coeff(A, B, C, D, t);
2652}
2653
2654/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2655 t value such that cubic(t) = target
2656 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002657static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002658 SkScalar target, SkScalar* t) {
2659 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2660 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002661
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002662 SkScalar D = c0 - target;
2663 SkScalar A = c3 + 3*(c1 - c2) - c0;
2664 SkScalar B = 3*(c2 - c1 - c1 + c0);
2665 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002666
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002667 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2668 SkScalar minT = 0;
2669 SkScalar maxT = SK_Scalar1;
2670 SkScalar mid;
2671 int i;
2672 for (i = 0; i < 16; i++) {
2673 mid = SkScalarAve(minT, maxT);
2674 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2675 if (delta < 0) {
2676 minT = mid;
2677 delta = -delta;
2678 } else {
2679 maxT = mid;
2680 }
2681 if (delta < TOLERANCE) {
2682 break;
2683 }
2684 }
2685 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002686}
2687
2688template <size_t N> static void find_minmax(const SkPoint pts[],
2689 SkScalar* minPtr, SkScalar* maxPtr) {
2690 SkScalar min, max;
2691 min = max = pts[0].fX;
2692 for (size_t i = 1; i < N; ++i) {
2693 min = SkMinScalar(min, pts[i].fX);
2694 max = SkMaxScalar(max, pts[i].fX);
2695 }
2696 *minPtr = min;
2697 *maxPtr = max;
2698}
2699
2700static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2701 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002702
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002703 int dir = 1;
2704 if (pts[0].fY > pts[3].fY) {
2705 storage[0] = pts[3];
2706 storage[1] = pts[2];
2707 storage[2] = pts[1];
2708 storage[3] = pts[0];
2709 pts = storage;
2710 dir = -1;
2711 }
2712 if (y < pts[0].fY || y >= pts[3].fY) {
2713 return 0;
2714 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002715
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002716 // quickreject or quickaccept
2717 SkScalar min, max;
2718 find_minmax<4>(pts, &min, &max);
2719 if (x < min) {
2720 return 0;
2721 }
2722 if (x > max) {
2723 return dir;
2724 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002726 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002727 SkScalar t;
2728 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2729 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 +00002730 return xt < x ? dir : 0;
2731}
2732
2733static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2734 SkPoint dst[10];
2735 int n = SkChopCubicAtYExtrema(pts, dst);
2736 int w = 0;
2737 for (int i = 0; i <= n; ++i) {
2738 w += winding_mono_cubic(&dst[i * 3], x, y);
2739 }
2740 return w;
2741}
2742
2743static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2744 SkScalar y0 = pts[0].fY;
2745 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002746
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747 int dir = 1;
2748 if (y0 > y2) {
2749 SkTSwap(y0, y2);
2750 dir = -1;
2751 }
2752 if (y < y0 || y >= y2) {
2753 return 0;
2754 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002755
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756 // bounds check on X (not required. is it faster?)
2757#if 0
2758 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2759 return 0;
2760 }
2761#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 SkScalar roots[2];
2764 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2765 2 * (pts[1].fY - pts[0].fY),
2766 pts[0].fY - y,
2767 roots);
2768 SkASSERT(n <= 1);
2769 SkScalar xt;
2770 if (0 == n) {
2771 SkScalar mid = SkScalarAve(y0, y2);
2772 // Need [0] and [2] if dir == 1
2773 // and [2] and [0] if dir == -1
2774 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2775 } else {
2776 SkScalar t = roots[0];
2777 SkScalar C = pts[0].fX;
2778 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2779 SkScalar B = 2 * (pts[1].fX - C);
2780 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2781 }
2782 return xt < x ? dir : 0;
2783}
2784
2785static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2786 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2787 if (y0 == y1) {
2788 return true;
2789 }
2790 if (y0 < y1) {
2791 return y1 <= y2;
2792 } else {
2793 return y1 >= y2;
2794 }
2795}
2796
2797static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2798 SkPoint dst[5];
2799 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002800
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002801 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2802 n = SkChopQuadAtYExtrema(pts, dst);
2803 pts = dst;
2804 }
2805 int w = winding_mono_quad(pts, x, y);
2806 if (n > 0) {
2807 w += winding_mono_quad(&pts[2], x, y);
2808 }
2809 return w;
2810}
2811
2812static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2813 SkScalar x0 = pts[0].fX;
2814 SkScalar y0 = pts[0].fY;
2815 SkScalar x1 = pts[1].fX;
2816 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 int dir = 1;
2821 if (y0 > y1) {
2822 SkTSwap(y0, y1);
2823 dir = -1;
2824 }
2825 if (y < y0 || y >= y1) {
2826 return 0;
2827 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2830 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002831
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002832 if (SkScalarSignAsInt(cross) == dir) {
2833 dir = 0;
2834 }
2835 return dir;
2836}
2837
reed@google.com4db592c2013-10-30 17:39:43 +00002838static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2839 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2840}
2841
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002842bool SkPath::contains(SkScalar x, SkScalar y) const {
2843 bool isInverse = this->isInverseFillType();
2844 if (this->isEmpty()) {
2845 return isInverse;
2846 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002847
reed@google.com4db592c2013-10-30 17:39:43 +00002848 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 return isInverse;
2850 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 SkPath::Iter iter(*this, true);
2853 bool done = false;
2854 int w = 0;
2855 do {
2856 SkPoint pts[4];
2857 switch (iter.next(pts, false)) {
2858 case SkPath::kMove_Verb:
2859 case SkPath::kClose_Verb:
2860 break;
2861 case SkPath::kLine_Verb:
2862 w += winding_line(pts, x, y);
2863 break;
2864 case SkPath::kQuad_Verb:
2865 w += winding_quad(pts, x, y);
2866 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002867 case SkPath::kConic_Verb:
2868 SkASSERT(0);
2869 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870 case SkPath::kCubic_Verb:
2871 w += winding_cubic(pts, x, y);
2872 break;
2873 case SkPath::kDone_Verb:
2874 done = true;
2875 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002876 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 switch (this->getFillType()) {
2880 case SkPath::kEvenOdd_FillType:
2881 case SkPath::kInverseEvenOdd_FillType:
2882 w &= 1;
2883 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002884 default:
2885 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 }
2887 return SkToBool(w);
2888}