blob: f8280bd6d21651724dd9e3501055d9e2754b975d [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) {
reeda8b326c2014-12-09 11:50:32 -08001290 if (radius == 0) {
1291 this->lineTo(x1, y1);
1292 return;
1293 }
1294
1295 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001296
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 // need to know our prev pt so we can construct tangent vectors
1298 {
1299 SkPoint start;
1300 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001301 // Handle degenerate cases by adding a line to the first point and
1302 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 before.setNormalize(x1 - start.fX, y1 - start.fY);
1304 after.setNormalize(x2 - x1, y2 - y1);
1305 }
reed@google.comabf15c12011-01-18 20:35:51 +00001306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307 SkScalar cosh = SkPoint::DotProduct(before, after);
1308 SkScalar sinh = SkPoint::CrossProduct(before, after);
1309
1310 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001311 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 return;
1313 }
reed@google.comabf15c12011-01-18 20:35:51 +00001314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1316 if (dist < 0) {
1317 dist = -dist;
1318 }
1319
1320 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1321 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1322 SkRotationDirection arcDir;
1323
1324 // now turn before/after into normals
1325 if (sinh > 0) {
1326 before.rotateCCW();
1327 after.rotateCCW();
1328 arcDir = kCW_SkRotationDirection;
1329 } else {
1330 before.rotateCW();
1331 after.rotateCW();
1332 arcDir = kCCW_SkRotationDirection;
1333 }
1334
1335 SkMatrix matrix;
1336 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001337
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 matrix.setScale(radius, radius);
1339 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1340 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001341
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001343
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 this->incReserve(count);
1345 // [xx,yy] == pts[0]
1346 this->lineTo(xx, yy);
1347 for (int i = 1; i < count; i += 2) {
1348 this->quadTo(pts[i], pts[i+1]);
1349 }
1350}
1351
1352///////////////////////////////////////////////////////////////////////////////
1353
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001354void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355 SkMatrix matrix;
1356
1357 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001358 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359}
1360
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001361void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001362 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001364 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 SkPoint pts[4];
1366 Verb verb;
1367
1368 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001369 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370 while ((verb = iter.next(pts)) != kDone_Verb) {
1371 switch (verb) {
1372 case kMove_Verb:
1373 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001374 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1375 injectMoveToIfNeeded(); // In case last contour is closed
1376 this->lineTo(pts[0]);
1377 } else {
1378 this->moveTo(pts[0]);
1379 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 break;
1381 case kLine_Verb:
1382 proc(matrix, &pts[1], &pts[1], 1);
1383 this->lineTo(pts[1]);
1384 break;
1385 case kQuad_Verb:
1386 proc(matrix, &pts[1], &pts[1], 2);
1387 this->quadTo(pts[1], pts[2]);
1388 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001389 case kConic_Verb:
1390 proc(matrix, &pts[1], &pts[1], 2);
1391 this->conicTo(pts[1], pts[2], iter.conicWeight());
1392 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393 case kCubic_Verb:
1394 proc(matrix, &pts[1], &pts[1], 3);
1395 this->cubicTo(pts[1], pts[2], pts[3]);
1396 break;
1397 case kClose_Verb:
1398 this->close();
1399 break;
1400 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001401 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001403 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 }
1405}
1406
1407///////////////////////////////////////////////////////////////////////////////
1408
reed@google.com277c3f82013-05-31 15:17:50 +00001409static int pts_in_verb(unsigned verb) {
1410 static const uint8_t gPtsInVerb[] = {
1411 1, // kMove
1412 1, // kLine
1413 2, // kQuad
1414 2, // kConic
1415 3, // kCubic
1416 0, // kClose
1417 0 // kDone
1418 };
1419
1420 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1421 return gPtsInVerb[verb];
1422}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424// ignore the last point of the 1st contour
1425void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001426 int i, vcount = path.fPathRef->countVerbs();
1427 // exit early if the path is empty, or just has a moveTo.
1428 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 return;
1430 }
1431
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001432 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001434 const uint8_t* verbs = path.fPathRef->verbs();
1435 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001436 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001438 SkASSERT(verbs[~0] == kMove_Verb);
1439 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001440 unsigned v = verbs[~i];
1441 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 if (n == 0) {
1443 break;
1444 }
1445 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001446 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 }
1448
1449 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001450 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 case kLine_Verb:
1452 this->lineTo(pts[-1].fX, pts[-1].fY);
1453 break;
1454 case kQuad_Verb:
1455 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1456 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001457 case kConic_Verb:
1458 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1459 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 case kCubic_Verb:
1461 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1462 pts[-3].fX, pts[-3].fY);
1463 break;
1464 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001465 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 break;
1467 }
reed@google.com277c3f82013-05-31 15:17:50 +00001468 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 }
1470}
1471
reed@google.com63d73742012-01-10 15:33:12 +00001472void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001474
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001475 const SkPoint* pts = src.fPathRef->pointsEnd();
1476 // we will iterator through src's verbs backwards
1477 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1478 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001479 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001480
1481 bool needMove = true;
1482 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 while (verbs < verbsEnd) {
1484 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001485 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001486
1487 if (needMove) {
1488 --pts;
1489 this->moveTo(pts->fX, pts->fY);
1490 needMove = false;
1491 }
1492 pts -= n;
1493 switch (v) {
1494 case kMove_Verb:
1495 if (needClose) {
1496 this->close();
1497 needClose = false;
1498 }
1499 needMove = true;
1500 pts += 1; // so we see the point in "if (needMove)" above
1501 break;
1502 case kLine_Verb:
1503 this->lineTo(pts[0]);
1504 break;
1505 case kQuad_Verb:
1506 this->quadTo(pts[1], pts[0]);
1507 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001508 case kConic_Verb:
1509 this->conicTo(pts[1], pts[0], *--conicWeights);
1510 break;
reed@google.com63d73742012-01-10 15:33:12 +00001511 case kCubic_Verb:
1512 this->cubicTo(pts[2], pts[1], pts[0]);
1513 break;
1514 case kClose_Verb:
1515 needClose = true;
1516 break;
1517 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001518 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001519 }
1520 }
1521}
1522
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523///////////////////////////////////////////////////////////////////////////////
1524
1525void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1526 SkMatrix matrix;
1527
1528 matrix.setTranslate(dx, dy);
1529 this->transform(matrix, dst);
1530}
1531
1532#include "SkGeometry.h"
1533
1534static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1535 int level = 2) {
1536 if (--level >= 0) {
1537 SkPoint tmp[5];
1538
1539 SkChopQuadAtHalf(pts, tmp);
1540 subdivide_quad_to(path, &tmp[0], level);
1541 subdivide_quad_to(path, &tmp[2], level);
1542 } else {
1543 path->quadTo(pts[1], pts[2]);
1544 }
1545}
1546
1547static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1548 int level = 2) {
1549 if (--level >= 0) {
1550 SkPoint tmp[7];
1551
1552 SkChopCubicAtHalf(pts, tmp);
1553 subdivide_cubic_to(path, &tmp[0], level);
1554 subdivide_cubic_to(path, &tmp[3], level);
1555 } else {
1556 path->cubicTo(pts[1], pts[2], pts[3]);
1557 }
1558}
1559
1560void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1561 SkDEBUGCODE(this->validate();)
1562 if (dst == NULL) {
1563 dst = (SkPath*)this;
1564 }
1565
tomhudson@google.com8d430182011-06-06 19:11:19 +00001566 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 SkPath tmp;
1568 tmp.fFillType = fFillType;
1569
1570 SkPath::Iter iter(*this, false);
1571 SkPoint pts[4];
1572 SkPath::Verb verb;
1573
reed@google.com4a3b7142012-05-16 17:16:46 +00001574 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575 switch (verb) {
1576 case kMove_Verb:
1577 tmp.moveTo(pts[0]);
1578 break;
1579 case kLine_Verb:
1580 tmp.lineTo(pts[1]);
1581 break;
1582 case kQuad_Verb:
1583 subdivide_quad_to(&tmp, pts);
1584 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001585 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001586 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001587 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1588 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 case kCubic_Verb:
1590 subdivide_cubic_to(&tmp, pts);
1591 break;
1592 case kClose_Verb:
1593 tmp.close();
1594 break;
1595 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001596 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 break;
1598 }
1599 }
1600
1601 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001602 SkPathRef::Editor ed(&dst->fPathRef);
1603 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001604 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001606 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1607
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001610 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001611 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001613
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001614 if (kUnknown_Direction == fDirection) {
1615 dst->fDirection = kUnknown_Direction;
1616 } else {
1617 SkScalar det2x2 =
1618 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1619 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1620 if (det2x2 < 0) {
1621 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1622 } else if (det2x2 > 0) {
1623 dst->fDirection = fDirection;
1624 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001625 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001626 dst->fDirection = kUnknown_Direction;
1627 }
1628 }
1629
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630 SkDEBUGCODE(dst->validate();)
1631 }
1632}
1633
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634///////////////////////////////////////////////////////////////////////////////
1635///////////////////////////////////////////////////////////////////////////////
1636
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001637enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001638 kEmptyContour_SegmentState, // The current contour is empty. We may be
1639 // starting processing or we may have just
1640 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001641 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1642 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1643 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644};
1645
1646SkPath::Iter::Iter() {
1647#ifdef SK_DEBUG
1648 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001649 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001651 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001652 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653#endif
1654 // need to init enough to make next() harmlessly return kDone_Verb
1655 fVerbs = NULL;
1656 fVerbStop = NULL;
1657 fNeedClose = false;
1658}
1659
1660SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1661 this->setPath(path, forceClose);
1662}
1663
1664void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001665 fPts = path.fPathRef->points();
1666 fVerbs = path.fPathRef->verbs();
1667 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001668 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001669 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001670 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 fForceClose = SkToU8(forceClose);
1672 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001673 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674}
1675
1676bool SkPath::Iter::isClosedContour() const {
1677 if (fVerbs == NULL || fVerbs == fVerbStop) {
1678 return false;
1679 }
1680 if (fForceClose) {
1681 return true;
1682 }
1683
1684 const uint8_t* verbs = fVerbs;
1685 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001686
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001687 if (kMove_Verb == *(verbs - 1)) {
1688 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 }
1690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001691 while (verbs > stop) {
1692 // verbs points one beyond the current verb, decrement first.
1693 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 if (kMove_Verb == v) {
1695 break;
1696 }
1697 if (kClose_Verb == v) {
1698 return true;
1699 }
1700 }
1701 return false;
1702}
1703
1704SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001705 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001707 // A special case: if both points are NaN, SkPoint::operation== returns
1708 // false, but the iterator expects that they are treated as the same.
1709 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001710 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1711 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001712 return kClose_Verb;
1713 }
1714
reed@google.com9e25dbf2012-05-15 17:05:38 +00001715 pts[0] = fLastPt;
1716 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 fLastPt = fMoveTo;
1718 fCloseLine = true;
1719 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001720 } else {
1721 pts[0] = fMoveTo;
1722 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724}
1725
reed@google.com9e25dbf2012-05-15 17:05:38 +00001726const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727 if (fSegmentState == kAfterMove_SegmentState) {
1728 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001729 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001730 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1733 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001734 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736}
1737
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738void SkPath::Iter::consumeDegenerateSegments() {
1739 // We need to step over anything that will not move the current draw point
1740 // forward before the next move is seen
1741 const uint8_t* lastMoveVerb = 0;
1742 const SkPoint* lastMovePt = 0;
1743 SkPoint lastPt = fLastPt;
1744 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001745 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001746 switch (verb) {
1747 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748 // Keep a record of this most recent move
1749 lastMoveVerb = fVerbs;
1750 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001751 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001752 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001753 fPts++;
1754 break;
1755
1756 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001757 // A close when we are in a segment is always valid except when it
1758 // follows a move which follows a segment.
1759 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 return;
1761 }
1762 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001764 break;
1765
1766 case kLine_Verb:
1767 if (!IsLineDegenerate(lastPt, fPts[0])) {
1768 if (lastMoveVerb) {
1769 fVerbs = lastMoveVerb;
1770 fPts = lastMovePt;
1771 return;
1772 }
1773 return;
1774 }
1775 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001776 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001777 fPts++;
1778 break;
1779
reed@google.com277c3f82013-05-31 15:17:50 +00001780 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001781 case kQuad_Verb:
1782 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1783 if (lastMoveVerb) {
1784 fVerbs = lastMoveVerb;
1785 fPts = lastMovePt;
1786 return;
1787 }
1788 return;
1789 }
1790 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001791 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001793 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794 break;
1795
1796 case kCubic_Verb:
1797 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1798 if (lastMoveVerb) {
1799 fVerbs = lastMoveVerb;
1800 fPts = lastMovePt;
1801 return;
1802 }
1803 return;
1804 }
1805 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001806 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807 fPts += 3;
1808 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001809
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001811 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812 }
1813 }
1814}
1815
reed@google.com4a3b7142012-05-16 17:16:46 +00001816SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001818
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 // Close the curve if requested and if there is some curve to close
1821 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001822 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 return kLine_Verb;
1824 }
1825 fNeedClose = false;
1826 return kClose_Verb;
1827 }
1828 return kDone_Verb;
1829 }
1830
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001831 // fVerbs is one beyond the current verb, decrement first
1832 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001833 const SkPoint* SK_RESTRICT srcPts = fPts;
1834 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835
1836 switch (verb) {
1837 case kMove_Verb:
1838 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001839 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 verb = this->autoClose(pts);
1841 if (verb == kClose_Verb) {
1842 fNeedClose = false;
1843 }
1844 return (Verb)verb;
1845 }
1846 if (fVerbs == fVerbStop) { // might be a trailing moveto
1847 return kDone_Verb;
1848 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001849 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001850 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001852 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 fNeedClose = fForceClose;
1855 break;
1856 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001857 pts[0] = this->cons_moveTo();
1858 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 fLastPt = srcPts[0];
1860 fCloseLine = false;
1861 srcPts += 1;
1862 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001863 case kConic_Verb:
1864 fConicWeights += 1;
1865 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001867 pts[0] = this->cons_moveTo();
1868 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 fLastPt = srcPts[1];
1870 srcPts += 2;
1871 break;
1872 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 pts[0] = this->cons_moveTo();
1874 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 fLastPt = srcPts[2];
1876 srcPts += 3;
1877 break;
1878 case kClose_Verb:
1879 verb = this->autoClose(pts);
1880 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 } else {
1883 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001884 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 break;
1888 }
1889 fPts = srcPts;
1890 return (Verb)verb;
1891}
1892
1893///////////////////////////////////////////////////////////////////////////////
1894
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001895SkPath::RawIter::RawIter() {
1896#ifdef SK_DEBUG
1897 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001898 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001899 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1900#endif
1901 // need to init enough to make next() harmlessly return kDone_Verb
1902 fVerbs = NULL;
1903 fVerbStop = NULL;
1904}
1905
1906SkPath::RawIter::RawIter(const SkPath& path) {
1907 this->setPath(path);
1908}
1909
1910void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001911 fPts = path.fPathRef->points();
1912 fVerbs = path.fPathRef->verbs();
1913 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001914 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001915 fMoveTo.fX = fMoveTo.fY = 0;
1916 fLastPt.fX = fLastPt.fY = 0;
1917}
1918
1919SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001920 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001921 if (fVerbs == fVerbStop) {
1922 return kDone_Verb;
1923 }
1924
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925 // fVerbs points one beyond next verb so decrement first.
1926 unsigned verb = *(--fVerbs);
1927 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001928
1929 switch (verb) {
1930 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001931 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001932 fMoveTo = srcPts[0];
1933 fLastPt = fMoveTo;
1934 srcPts += 1;
1935 break;
1936 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001937 pts[0] = fLastPt;
1938 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001939 fLastPt = srcPts[0];
1940 srcPts += 1;
1941 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001942 case kConic_Verb:
1943 fConicWeights += 1;
1944 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001945 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001946 pts[0] = fLastPt;
1947 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001948 fLastPt = srcPts[1];
1949 srcPts += 2;
1950 break;
1951 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001952 pts[0] = fLastPt;
1953 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001954 fLastPt = srcPts[2];
1955 srcPts += 3;
1956 break;
1957 case kClose_Verb:
1958 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001959 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001960 break;
1961 }
1962 fPts = srcPts;
1963 return (Verb)verb;
1964}
1965
1966///////////////////////////////////////////////////////////////////////////////
1967
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001969 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970*/
1971
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001972size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 SkDEBUGCODE(this->validate();)
1974
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001975 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001976 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001977 return SkAlign4(byteCount);
1978 }
1979
1980 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001981
robertphillips@google.com466310d2013-12-03 16:43:54 +00001982 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001983 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001984 (fDirection << kDirection_SerializationShift) |
1985 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001986
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001987 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001988
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001989 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001990
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001991 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001992 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993}
1994
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001995size_t SkPath::readFromMemory(const void* storage, size_t length) {
1996 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001997
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001998 int32_t packed;
1999 if (!buffer.readS32(&packed)) {
2000 return 0;
2001 }
2002
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002003 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2004 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002005 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002006 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002007 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002008
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002009 size_t sizeRead = 0;
2010 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002011 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002012 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002013 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002014 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002015 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002016 // If the buffer is not valid, pathRef should be NULL
2017 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002018 }
2019 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020}
2021
2022///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023
reede05fed02014-12-15 07:59:53 -08002024#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002025#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002026
reed@google.com51bbe752013-01-17 22:07:50 +00002027static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002028 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002029 str->append(label);
2030 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002031
reed@google.com51bbe752013-01-17 22:07:50 +00002032 const SkScalar* values = &pts[0].fX;
2033 count *= 2;
2034
2035 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002036 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002037 if (i < count - 1) {
2038 str->append(", ");
2039 }
2040 }
reed@google.com277c3f82013-05-31 15:17:50 +00002041 if (conicWeight >= 0) {
2042 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002043 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002044 }
caryclark08fa28c2014-10-23 13:08:56 -07002045 str->append(");");
reede05fed02014-12-15 07:59:53 -08002046 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002047 str->append(" // ");
2048 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002049 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002050 if (i < count - 1) {
2051 str->append(", ");
2052 }
2053 }
2054 if (conicWeight >= 0) {
2055 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002056 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002057 }
2058 }
2059 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002060}
2061
caryclarke9562592014-09-15 09:26:09 -07002062void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002063 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 Iter iter(*this, forceClose);
2065 SkPoint pts[4];
2066 Verb verb;
2067
caryclark66a5d8b2014-06-24 08:30:15 -07002068 if (!wStream) {
2069 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2070 }
reed@google.com51bbe752013-01-17 22:07:50 +00002071 SkString builder;
2072
reed@google.com4a3b7142012-05-16 17:16:46 +00002073 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 switch (verb) {
2075 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002076 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077 break;
2078 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002079 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 break;
2081 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002082 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002084 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002085 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002086 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002088 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089 break;
2090 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002091 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002092 break;
2093 default:
2094 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2095 verb = kDone_Verb; // stop the loop
2096 break;
2097 }
2098 }
caryclark66a5d8b2014-06-24 08:30:15 -07002099 if (wStream) {
2100 wStream->writeText(builder.c_str());
2101 } else {
2102 SkDebugf("%s", builder.c_str());
2103 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002104}
2105
reed@android.come522ca52009-11-23 20:10:41 +00002106void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002107 this->dump(NULL, false, false);
2108}
2109
2110void SkPath::dumpHex() const {
2111 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002112}
2113
2114#ifdef SK_DEBUG
2115void SkPath::validate() const {
2116 SkASSERT(this != NULL);
2117 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002118
djsollen@google.com077348c2012-10-22 20:23:32 +00002119#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002120 if (!fBoundsIsDirty) {
2121 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002122
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002123 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002124 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002125
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002126 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002127 // if we're empty, fBounds may be empty but translated, so we can't
2128 // necessarily compare to bounds directly
2129 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2130 // be [2, 2, 2, 2]
2131 SkASSERT(bounds.isEmpty());
2132 SkASSERT(fBounds.isEmpty());
2133 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002134 if (bounds.isEmpty()) {
2135 SkASSERT(fBounds.isEmpty());
2136 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002137 if (!fBounds.isEmpty()) {
2138 SkASSERT(fBounds.contains(bounds));
2139 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002140 }
reed@android.come522ca52009-11-23 20:10:41 +00002141 }
2142 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002143#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002144}
djsollen@google.com077348c2012-10-22 20:23:32 +00002145#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002146
reed@google.com04863fa2011-05-15 04:08:24 +00002147///////////////////////////////////////////////////////////////////////////////
2148
reed@google.com0b7b9822011-05-16 12:29:27 +00002149static int sign(SkScalar x) { return x < 0; }
2150#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002151
robertphillipsc506e302014-09-16 09:43:31 -07002152enum DirChange {
2153 kLeft_DirChange,
2154 kRight_DirChange,
2155 kStraight_DirChange,
2156 kBackwards_DirChange,
2157
2158 kInvalid_DirChange
2159};
2160
2161
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002162static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002163 // The error epsilon was empirically derived; worse case round rects
2164 // with a mid point outset by 2x float epsilon in tests had an error
2165 // of 12.
2166 const int epsilon = 16;
2167 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2168 return false;
2169 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002170 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002171 int aBits = SkFloatAs2sCompliment(compA);
2172 int bBits = SkFloatAs2sCompliment(compB);
2173 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002174}
2175
robertphillipsc506e302014-09-16 09:43:31 -07002176static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2177 const SkVector& lastVec, const SkVector& curVec) {
2178 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2179
2180 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2181 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2182 largest = SkTMax(largest, -smallest);
2183
2184 if (!almost_equal(largest, largest + cross)) {
2185 int sign = SkScalarSignAsInt(cross);
2186 if (sign) {
2187 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2188 }
2189 }
2190
2191 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2192 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2193 lastVec.dot(curVec) < 0.0f) {
2194 return kBackwards_DirChange;
2195 }
2196
2197 return kStraight_DirChange;
2198}
2199
reed@google.com04863fa2011-05-15 04:08:24 +00002200// only valid for a single contour
2201struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002202 Convexicator()
2203 : fPtCount(0)
2204 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002205 , fDirection(SkPath::kUnknown_Direction)
2206 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002207 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002208 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002209 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002210 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002211 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002212 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002213
2214 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002215 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002216 }
2217
2218 SkPath::Convexity getConvexity() const { return fConvexity; }
2219
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002220 /** The direction returned is only valid if the path is determined convex */
2221 SkPath::Direction getDirection() const { return fDirection; }
2222
reed@google.com04863fa2011-05-15 04:08:24 +00002223 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002224 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002225 return;
2226 }
2227
2228 if (0 == fPtCount) {
2229 fCurrPt = pt;
2230 ++fPtCount;
2231 } else {
2232 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002233 SkScalar lengthSqd = vec.lengthSqd();
2234 if (!SkScalarIsFinite(lengthSqd)) {
2235 fIsFinite = false;
2236 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002238 fCurrPt = pt;
2239 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002240 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002241 } else {
2242 SkASSERT(fPtCount > 2);
2243 this->addVec(vec);
2244 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002245
reed@google.com85b6e392011-05-15 20:25:17 +00002246 int sx = sign(vec.fX);
2247 int sy = sign(vec.fY);
2248 fDx += (sx != fSx);
2249 fDy += (sy != fSy);
2250 fSx = sx;
2251 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002252
reed@google.com85b6e392011-05-15 20:25:17 +00002253 if (fDx > 3 || fDy > 3) {
2254 fConvexity = SkPath::kConcave_Convexity;
2255 }
reed@google.com04863fa2011-05-15 04:08:24 +00002256 }
2257 }
2258 }
2259
2260 void close() {
2261 if (fPtCount > 2) {
2262 this->addVec(fFirstVec);
2263 }
2264 }
2265
caryclarkd3d1a982014-12-08 04:57:38 -08002266 bool isFinite() const {
2267 return fIsFinite;
2268 }
2269
reed@google.com04863fa2011-05-15 04:08:24 +00002270private:
2271 void addVec(const SkVector& vec) {
2272 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002273 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2274 switch (dir) {
2275 case kLeft_DirChange: // fall through
2276 case kRight_DirChange:
2277 if (kInvalid_DirChange == fExpectedDir) {
2278 fExpectedDir = dir;
2279 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2280 : SkPath::kCCW_Direction;
2281 } else if (dir != fExpectedDir) {
2282 fConvexity = SkPath::kConcave_Convexity;
2283 fDirection = SkPath::kUnknown_Direction;
2284 }
2285 fLastVec = vec;
2286 break;
2287 case kStraight_DirChange:
2288 break;
2289 case kBackwards_DirChange:
2290 fLastVec = vec;
2291 break;
2292 case kInvalid_DirChange:
2293 SkFAIL("Use of invalid direction change flag");
2294 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 }
2296 }
2297
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002298 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002300 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2301 // value with the current vec is deemed to be of a significant value.
2302 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002303 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002304 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002305 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002306 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002307 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002308 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002309};
2310
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002311SkPath::Convexity SkPath::internalGetConvexity() const {
2312 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002313 SkPoint pts[4];
2314 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002315 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002316
2317 int contourCount = 0;
2318 int count;
2319 Convexicator state;
2320
caryclarkd3d1a982014-12-08 04:57:38 -08002321 if (!isFinite()) {
2322 return kUnknown_Convexity;
2323 }
reed@google.com04863fa2011-05-15 04:08:24 +00002324 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2325 switch (verb) {
2326 case kMove_Verb:
2327 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 return kConcave_Convexity;
2330 }
2331 pts[1] = pts[0];
2332 count = 1;
2333 break;
2334 case kLine_Verb: count = 1; break;
2335 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002336 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002337 case kCubic_Verb: count = 3; break;
2338 case kClose_Verb:
2339 state.close();
2340 count = 0;
2341 break;
2342 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002343 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002344 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002345 return kConcave_Convexity;
2346 }
2347
2348 for (int i = 1; i <= count; i++) {
2349 state.addPt(pts[i]);
2350 }
2351 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002352 if (!state.isFinite()) {
2353 return kUnknown_Convexity;
2354 }
reed@google.com04863fa2011-05-15 04:08:24 +00002355 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002357 return kConcave_Convexity;
2358 }
2359 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002360 fConvexity = state.getConvexity();
2361 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2362 fDirection = state.getDirection();
2363 }
2364 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002365}
reed@google.com69a99432012-01-10 18:00:10 +00002366
2367///////////////////////////////////////////////////////////////////////////////
2368
2369class ContourIter {
2370public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002371 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002372
2373 bool done() const { return fDone; }
2374 // if !done() then these may be called
2375 int count() const { return fCurrPtCount; }
2376 const SkPoint* pts() const { return fCurrPt; }
2377 void next();
2378
2379private:
2380 int fCurrPtCount;
2381 const SkPoint* fCurrPt;
2382 const uint8_t* fCurrVerb;
2383 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002384 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002385 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002386 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002387};
2388
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002389ContourIter::ContourIter(const SkPathRef& pathRef) {
2390 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002391 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002392 fCurrPt = pathRef.points();
2393 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002394 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002395 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002396 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002397 this->next();
2398}
2399
2400void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002401 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002402 fDone = true;
2403 }
2404 if (fDone) {
2405 return;
2406 }
2407
2408 // skip pts of prev contour
2409 fCurrPt += fCurrPtCount;
2410
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002411 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002412 int ptCount = 1; // moveTo
2413 const uint8_t* verbs = fCurrVerb;
2414
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002415 for (--verbs; verbs > fStopVerbs; --verbs) {
2416 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002417 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002418 goto CONTOUR_END;
2419 case SkPath::kLine_Verb:
2420 ptCount += 1;
2421 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002422 case SkPath::kConic_Verb:
2423 fCurrConicWeight += 1;
2424 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002425 case SkPath::kQuad_Verb:
2426 ptCount += 2;
2427 break;
2428 case SkPath::kCubic_Verb:
2429 ptCount += 3;
2430 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002431 case SkPath::kClose_Verb:
2432 break;
2433 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002434 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002435 break;
2436 }
2437 }
2438CONTOUR_END:
2439 fCurrPtCount = ptCount;
2440 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002441 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002442}
2443
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002444// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002445static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002446 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2447 // We may get 0 when the above subtracts underflow. We expect this to be
2448 // very rare and lazily promote to double.
2449 if (0 == cross) {
2450 double p0x = SkScalarToDouble(p0.fX);
2451 double p0y = SkScalarToDouble(p0.fY);
2452
2453 double p1x = SkScalarToDouble(p1.fX);
2454 double p1y = SkScalarToDouble(p1.fY);
2455
2456 double p2x = SkScalarToDouble(p2.fX);
2457 double p2y = SkScalarToDouble(p2.fY);
2458
2459 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2460 (p1y - p0y) * (p2x - p0x));
2461
2462 }
2463 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002464}
2465
reed@google.comc1ea60a2012-01-31 15:15:36 +00002466// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002467static int find_max_y(const SkPoint pts[], int count) {
2468 SkASSERT(count > 0);
2469 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002470 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002471 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002472 SkScalar y = pts[i].fY;
2473 if (y > max) {
2474 max = y;
2475 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002476 }
2477 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002478 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002479}
2480
reed@google.comcabaf1d2012-01-11 21:03:05 +00002481static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2482 int i = index;
2483 for (;;) {
2484 i = (i + inc) % n;
2485 if (i == index) { // we wrapped around, so abort
2486 break;
2487 }
2488 if (pts[index] != pts[i]) { // found a different point, success!
2489 break;
2490 }
2491 }
2492 return i;
2493}
2494
reed@google.comc1ea60a2012-01-31 15:15:36 +00002495/**
2496 * Starting at index, and moving forward (incrementing), find the xmin and
2497 * xmax of the contiguous points that have the same Y.
2498 */
2499static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2500 int* maxIndexPtr) {
2501 const SkScalar y = pts[index].fY;
2502 SkScalar min = pts[index].fX;
2503 SkScalar max = min;
2504 int minIndex = index;
2505 int maxIndex = index;
2506 for (int i = index + 1; i < n; ++i) {
2507 if (pts[i].fY != y) {
2508 break;
2509 }
2510 SkScalar x = pts[i].fX;
2511 if (x < min) {
2512 min = x;
2513 minIndex = i;
2514 } else if (x > max) {
2515 max = x;
2516 maxIndex = i;
2517 }
2518 }
2519 *maxIndexPtr = maxIndex;
2520 return minIndex;
2521}
2522
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002523static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002524 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002525}
2526
reed@google.comac8543f2012-01-30 20:51:25 +00002527/*
2528 * We loop through all contours, and keep the computed cross-product of the
2529 * contour that contained the global y-max. If we just look at the first
2530 * contour, we may find one that is wound the opposite way (correctly) since
2531 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2532 * that is outer most (or at least has the global y-max) before we can consider
2533 * its cross product.
2534 */
reed@google.com69a99432012-01-10 18:00:10 +00002535bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002536 if (kUnknown_Direction != fDirection) {
2537 *dir = static_cast<Direction>(fDirection);
2538 return true;
2539 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002540
2541 // don't want to pay the cost for computing this if it
2542 // is unknown, so we don't call isConvex()
2543 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2544 SkASSERT(kUnknown_Direction == fDirection);
2545 *dir = static_cast<Direction>(fDirection);
2546 return false;
2547 }
reed@google.com69a99432012-01-10 18:00:10 +00002548
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002549 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002550
reed@google.comac8543f2012-01-30 20:51:25 +00002551 // initialize with our logical y-min
2552 SkScalar ymax = this->getBounds().fTop;
2553 SkScalar ymaxCross = 0;
2554
reed@google.com69a99432012-01-10 18:00:10 +00002555 for (; !iter.done(); iter.next()) {
2556 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002557 if (n < 3) {
2558 continue;
2559 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002560
reed@google.comcabaf1d2012-01-11 21:03:05 +00002561 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002562 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002563 int index = find_max_y(pts, n);
2564 if (pts[index].fY < ymax) {
2565 continue;
2566 }
2567
2568 // If there is more than 1 distinct point at the y-max, we take the
2569 // x-min and x-max of them and just subtract to compute the dir.
2570 if (pts[(index + 1) % n].fY == pts[index].fY) {
2571 int maxIndex;
2572 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2573 if (minIndex == maxIndex) {
2574 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002575 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002576 SkASSERT(pts[minIndex].fY == pts[index].fY);
2577 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2578 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2579 // we just subtract the indices, and let that auto-convert to
2580 // SkScalar, since we just want - or + to signal the direction.
2581 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002582 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002583 TRY_CROSSPROD:
2584 // Find a next and prev index to use for the cross-product test,
2585 // but we try to find pts that form non-zero vectors from pts[index]
2586 //
2587 // Its possible that we can't find two non-degenerate vectors, so
2588 // we have to guard our search (e.g. all the pts could be in the
2589 // same place).
2590
2591 // we pass n - 1 instead of -1 so we don't foul up % operator by
2592 // passing it a negative LH argument.
2593 int prev = find_diff_pt(pts, index, n, n - 1);
2594 if (prev == index) {
2595 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002596 continue;
2597 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002598 int next = find_diff_pt(pts, index, n, 1);
2599 SkASSERT(next != index);
2600 cross = cross_prod(pts[prev], pts[index], pts[next]);
2601 // if we get a zero and the points are horizontal, then we look at the spread in
2602 // x-direction. We really should continue to walk away from the degeneracy until
2603 // there is a divergence.
2604 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2605 // construct the subtract so we get the correct Direction below
2606 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002607 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002608 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002609
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002610 if (cross) {
2611 // record our best guess so far
2612 ymax = pts[index].fY;
2613 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002614 }
2615 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002616 if (ymaxCross) {
2617 crossToDir(ymaxCross, dir);
2618 fDirection = *dir;
2619 return true;
2620 } else {
2621 return false;
2622 }
reed@google.comac8543f2012-01-30 20:51:25 +00002623}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002624
2625///////////////////////////////////////////////////////////////////////////////
2626
2627static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2628 SkScalar D, SkScalar t) {
2629 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2630}
2631
2632static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2633 SkScalar t) {
2634 SkScalar A = c3 + 3*(c1 - c2) - c0;
2635 SkScalar B = 3*(c2 - c1 - c1 + c0);
2636 SkScalar C = 3*(c1 - c0);
2637 SkScalar D = c0;
2638 return eval_cubic_coeff(A, B, C, D, t);
2639}
2640
2641/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2642 t value such that cubic(t) = target
2643 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002644static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002645 SkScalar target, SkScalar* t) {
2646 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2647 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002648
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002649 SkScalar D = c0 - target;
2650 SkScalar A = c3 + 3*(c1 - c2) - c0;
2651 SkScalar B = 3*(c2 - c1 - c1 + c0);
2652 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002653
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002654 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2655 SkScalar minT = 0;
2656 SkScalar maxT = SK_Scalar1;
2657 SkScalar mid;
2658 int i;
2659 for (i = 0; i < 16; i++) {
2660 mid = SkScalarAve(minT, maxT);
2661 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2662 if (delta < 0) {
2663 minT = mid;
2664 delta = -delta;
2665 } else {
2666 maxT = mid;
2667 }
2668 if (delta < TOLERANCE) {
2669 break;
2670 }
2671 }
2672 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002673}
2674
2675template <size_t N> static void find_minmax(const SkPoint pts[],
2676 SkScalar* minPtr, SkScalar* maxPtr) {
2677 SkScalar min, max;
2678 min = max = pts[0].fX;
2679 for (size_t i = 1; i < N; ++i) {
2680 min = SkMinScalar(min, pts[i].fX);
2681 max = SkMaxScalar(max, pts[i].fX);
2682 }
2683 *minPtr = min;
2684 *maxPtr = max;
2685}
2686
2687static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2688 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002689
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690 int dir = 1;
2691 if (pts[0].fY > pts[3].fY) {
2692 storage[0] = pts[3];
2693 storage[1] = pts[2];
2694 storage[2] = pts[1];
2695 storage[3] = pts[0];
2696 pts = storage;
2697 dir = -1;
2698 }
2699 if (y < pts[0].fY || y >= pts[3].fY) {
2700 return 0;
2701 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002702
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002703 // quickreject or quickaccept
2704 SkScalar min, max;
2705 find_minmax<4>(pts, &min, &max);
2706 if (x < min) {
2707 return 0;
2708 }
2709 if (x > max) {
2710 return dir;
2711 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002712
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002713 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002714 SkScalar t;
2715 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2716 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 +00002717 return xt < x ? dir : 0;
2718}
2719
2720static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2721 SkPoint dst[10];
2722 int n = SkChopCubicAtYExtrema(pts, dst);
2723 int w = 0;
2724 for (int i = 0; i <= n; ++i) {
2725 w += winding_mono_cubic(&dst[i * 3], x, y);
2726 }
2727 return w;
2728}
2729
2730static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2731 SkScalar y0 = pts[0].fY;
2732 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002733
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002734 int dir = 1;
2735 if (y0 > y2) {
2736 SkTSwap(y0, y2);
2737 dir = -1;
2738 }
2739 if (y < y0 || y >= y2) {
2740 return 0;
2741 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002742
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002743 // bounds check on X (not required. is it faster?)
2744#if 0
2745 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2746 return 0;
2747 }
2748#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002749
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002750 SkScalar roots[2];
2751 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2752 2 * (pts[1].fY - pts[0].fY),
2753 pts[0].fY - y,
2754 roots);
2755 SkASSERT(n <= 1);
2756 SkScalar xt;
2757 if (0 == n) {
2758 SkScalar mid = SkScalarAve(y0, y2);
2759 // Need [0] and [2] if dir == 1
2760 // and [2] and [0] if dir == -1
2761 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2762 } else {
2763 SkScalar t = roots[0];
2764 SkScalar C = pts[0].fX;
2765 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2766 SkScalar B = 2 * (pts[1].fX - C);
2767 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2768 }
2769 return xt < x ? dir : 0;
2770}
2771
2772static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2773 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2774 if (y0 == y1) {
2775 return true;
2776 }
2777 if (y0 < y1) {
2778 return y1 <= y2;
2779 } else {
2780 return y1 >= y2;
2781 }
2782}
2783
2784static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2785 SkPoint dst[5];
2786 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2789 n = SkChopQuadAtYExtrema(pts, dst);
2790 pts = dst;
2791 }
2792 int w = winding_mono_quad(pts, x, y);
2793 if (n > 0) {
2794 w += winding_mono_quad(&pts[2], x, y);
2795 }
2796 return w;
2797}
2798
2799static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2800 SkScalar x0 = pts[0].fX;
2801 SkScalar y0 = pts[0].fY;
2802 SkScalar x1 = pts[1].fX;
2803 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002804
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002806
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 int dir = 1;
2808 if (y0 > y1) {
2809 SkTSwap(y0, y1);
2810 dir = -1;
2811 }
2812 if (y < y0 || y >= y1) {
2813 return 0;
2814 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2817 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002818
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002819 if (SkScalarSignAsInt(cross) == dir) {
2820 dir = 0;
2821 }
2822 return dir;
2823}
2824
reed@google.com4db592c2013-10-30 17:39:43 +00002825static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2826 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2827}
2828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829bool SkPath::contains(SkScalar x, SkScalar y) const {
2830 bool isInverse = this->isInverseFillType();
2831 if (this->isEmpty()) {
2832 return isInverse;
2833 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002834
reed@google.com4db592c2013-10-30 17:39:43 +00002835 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 return isInverse;
2837 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002838
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 SkPath::Iter iter(*this, true);
2840 bool done = false;
2841 int w = 0;
2842 do {
2843 SkPoint pts[4];
2844 switch (iter.next(pts, false)) {
2845 case SkPath::kMove_Verb:
2846 case SkPath::kClose_Verb:
2847 break;
2848 case SkPath::kLine_Verb:
2849 w += winding_line(pts, x, y);
2850 break;
2851 case SkPath::kQuad_Verb:
2852 w += winding_quad(pts, x, y);
2853 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002854 case SkPath::kConic_Verb:
2855 SkASSERT(0);
2856 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 case SkPath::kCubic_Verb:
2858 w += winding_cubic(pts, x, y);
2859 break;
2860 case SkPath::kDone_Verb:
2861 done = true;
2862 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002863 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002865
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 switch (this->getFillType()) {
2867 case SkPath::kEvenOdd_FillType:
2868 case SkPath::kInverseEvenOdd_FillType:
2869 w &= 1;
2870 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002871 default:
2872 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 }
2874 return SkToBool(w);
2875}