blob: 4dab6042a2b5d7e942293a6ca64049ad0fcd7454 [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
10#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000011#include "SkReader32.h"
12#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
14
reed@google.com744faba2012-05-29 19:54:52 +000015// This value is just made-up for now. When count is 4, calling memset was much
16// slower than just writing the loop. This seems odd, and hopefully in the
17// future this we appear to have been a fluke...
18#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
19
reed@android.com8a1c16f2008-12-17 15:59:43 +000020////////////////////////////////////////////////////////////////////////////
21
reed@google.com3563c9e2011-11-14 19:34:57 +000022/**
23 * Path.bounds is defined to be the bounds of all the control points.
24 * If we called bounds.join(r) we would skip r if r was empty, which breaks
25 * our promise. Hence we have a custom joiner that doesn't look at emptiness
26 */
27static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
28 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
29 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
30 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
31 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
32}
33
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000034static bool is_degenerate(const SkPath& path) {
35 SkPath::Iter iter(path, false);
36 SkPoint pts[4];
37 return SkPath::kDone_Verb == iter.next(pts);
38}
39
bsalomon@google.com6aa29652012-04-18 13:29:52 +000040class SkAutoDisableOvalCheck {
41public:
42 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
43 fSaved = fPath->fIsOval;
44 }
45
46 ~SkAutoDisableOvalCheck() {
47 fPath->fIsOval = fSaved;
48 }
49
50private:
51 SkPath* fPath;
52 bool fSaved;
53};
54
reed@android.com8a1c16f2008-12-17 15:59:43 +000055/* This guy's constructor/destructor bracket a path editing operation. It is
56 used when we know the bounds of the amount we are going to add to the path
57 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 It captures some state about the path up front (i.e. if it already has a
60 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000061 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000062
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000063 It also notes if the path was originally degenerate, and if so, sets
64 isConvex to true. Thus it can only be used if the contour being added is
65 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 */
67class SkAutoPathBoundsUpdate {
68public:
69 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
70 this->init(path);
71 }
72
73 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
74 SkScalar right, SkScalar bottom) {
75 fRect.set(left, top, right, bottom);
76 this->init(path);
77 }
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000080 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000082 fPath->fBounds = fRect;
83 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000085 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000086 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 }
88 }
reed@google.comabf15c12011-01-18 20:35:51 +000089
reed@android.com8a1c16f2008-12-17 15:59:43 +000090private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 SkPath* fPath;
92 SkRect fRect;
93 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000094 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000095 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000100 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000103 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000104 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
107
reed@android.comd252db02009-04-01 18:31:44 +0000108static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
110 bounds->set(0, 0, 0, 0);
111 } else {
112 bounds->set(pts.begin(), pts.count());
113// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
114 }
115}
116
117////////////////////////////////////////////////////////////////////////////
118
119/*
120 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000121 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
123
124 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000125 1. If we encounter degenerate segments, remove them
126 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
127 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
128 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129*/
130
131////////////////////////////////////////////////////////////////////////////
132
reed@google.comd335d1d2012-01-12 18:17:11 +0000133// flag to require a moveTo if we begin with something else, like lineTo etc.
134#define INITIAL_LASTMOVETOINDEX_VALUE ~0
135
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000136SkPath::SkPath()
137 : fFillType(kWinding_FillType)
138 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000139 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000140 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000141 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000142 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000143#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000144 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000145 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000146#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000147}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148
149SkPath::SkPath(const SkPath& src) {
150 SkDEBUGCODE(src.validate();)
151 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000152#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000153 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000154 fGenerationID = src.fGenerationID;
155 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000156#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157}
158
159SkPath::~SkPath() {
160 SkDEBUGCODE(this->validate();)
161}
162
163SkPath& SkPath::operator=(const SkPath& src) {
164 SkDEBUGCODE(src.validate();)
165
166 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000167 fBounds = src.fBounds;
168 fPts = src.fPts;
169 fVerbs = src.fVerbs;
170 fFillType = src.fFillType;
171 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000172 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000173 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000174 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000175 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000176 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177 }
178 SkDEBUGCODE(this->validate();)
179 return *this;
180}
181
reed@android.com3abec1d2009-03-02 05:36:20 +0000182bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000183 // note: don't need to look at isConvex or bounds, since just comparing the
184 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000185
186 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
187 // since it is only a cache of info in the fVerbs, but its a fast way to
188 // notice a difference
189
reed@android.com3abec1d2009-03-02 05:36:20 +0000190 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000191 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
192 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000193}
194
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195void SkPath::swap(SkPath& other) {
196 SkASSERT(&other != NULL);
197
198 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000199 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 fPts.swap(other.fPts);
201 fVerbs.swap(other.fVerbs);
202 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000203 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000204 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000205 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000206 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000207 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000208 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 }
210}
211
djsollen@google.com56c69772011-11-08 19:00:26 +0000212#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000213uint32_t SkPath::getGenerationID() const {
214 return fGenerationID;
215}
djsollen@google.come63793a2012-03-21 15:39:03 +0000216
217const SkPath* SkPath::getSourcePath() const {
218 return fSourcePath;
219}
220
221void SkPath::setSourcePath(const SkPath* path) {
222 fSourcePath = path;
223}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000224#endif
225
reed@android.com8a1c16f2008-12-17 15:59:43 +0000226void SkPath::reset() {
227 SkDEBUGCODE(this->validate();)
228
229 fPts.reset();
230 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000231 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000232 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000233 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000234 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000235 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000236 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237}
238
239void SkPath::rewind() {
240 SkDEBUGCODE(this->validate();)
241
242 fPts.rewind();
243 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000244 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000245 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000246 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000247 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000248 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000249 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250}
251
252bool SkPath::isEmpty() const {
253 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000254 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255}
256
reed@google.com7e6c4d12012-05-10 14:05:43 +0000257bool SkPath::isLine(SkPoint line[2]) const {
258 int verbCount = fVerbs.count();
259 int ptCount = fPts.count();
260
261 if (2 == verbCount && 2 == ptCount) {
262 const uint8_t* verbs = fVerbs.begin();
263 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
264 if (line) {
265 const SkPoint* pts = fPts.begin();
266 line[0] = pts[0];
267 line[1] = pts[1];
268 }
269 return true;
270 }
271 }
272 return false;
273}
274
caryclark@google.comf1316942011-07-26 19:54:45 +0000275/*
276 Determines if path is a rect by keeping track of changes in direction
277 and looking for a loop either clockwise or counterclockwise.
278
279 The direction is computed such that:
280 0: vertical up
281 1: horizontal right
282 2: vertical down
283 3: horizontal left
284
285A rectangle cycles up/right/down/left or up/left/down/right.
286
287The test fails if:
288 The path is closed, and followed by a line.
289 A second move creates a new endpoint.
290 A diagonal line is parsed.
291 There's more than four changes of direction.
292 There's a discontinuity on the line (e.g., a move in the middle)
293 The line reverses direction.
294 The rectangle doesn't complete a cycle.
295 The path contains a quadratic or cubic.
296 The path contains fewer than four points.
297 The final point isn't equal to the first point.
298
299It's OK if the path has:
300 Several colinear line segments composing a rectangle side.
301 Single points on the rectangle side.
302
303The direction takes advantage of the corners found since opposite sides
304must travel in opposite directions.
305
306FIXME: Allow colinear quads and cubics to be treated like lines.
307FIXME: If the API passes fill-only, return true if the filled stroke
308 is a rectangle, though the caller failed to close the path.
309 */
310bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000312
caryclark@google.comf1316942011-07-26 19:54:45 +0000313 int corners = 0;
314 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000315 first.set(0, 0);
316 last.set(0, 0);
317 int firstDirection = 0;
318 int lastDirection = 0;
319 int nextDirection = 0;
320 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000321 bool autoClose = false;
322 const uint8_t* verbs = fVerbs.begin();
323 const uint8_t* verbStop = fVerbs.end();
324 const SkPoint* pts = fPts.begin();
325 while (verbs != verbStop) {
326 switch (*verbs++) {
327 case kClose_Verb:
328 pts = fPts.begin();
329 autoClose = true;
330 case kLine_Verb: {
331 SkScalar left = last.fX;
332 SkScalar top = last.fY;
333 SkScalar right = pts->fX;
334 SkScalar bottom = pts->fY;
335 ++pts;
336 if (left != right && top != bottom) {
337 return false; // diagonal
338 }
339 if (left == right && top == bottom) {
340 break; // single point on side OK
341 }
342 nextDirection = (left != right) << 0 |
343 (left < right || top < bottom) << 1;
344 if (0 == corners) {
345 firstDirection = nextDirection;
346 first = last;
347 last = pts[-1];
348 corners = 1;
349 closedOrMoved = false;
350 break;
351 }
352 if (closedOrMoved) {
353 return false; // closed followed by a line
354 }
355 closedOrMoved = autoClose;
356 if (lastDirection != nextDirection) {
357 if (++corners > 4) {
358 return false; // too many direction changes
359 }
360 }
361 last = pts[-1];
362 if (lastDirection == nextDirection) {
363 break; // colinear segment
364 }
365 // Possible values for corners are 2, 3, and 4.
366 // When corners == 3, nextDirection opposes firstDirection.
367 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000368 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000369 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
370 if ((directionCycle ^ turn) != nextDirection) {
371 return false; // direction didn't follow cycle
372 }
373 break;
374 }
375 case kQuad_Verb:
376 case kCubic_Verb:
377 return false; // quadratic, cubic not allowed
378 case kMove_Verb:
379 last = *pts++;
380 closedOrMoved = true;
381 break;
382 }
383 lastDirection = nextDirection;
384 }
385 // Success if 4 corners and first point equals last
386 bool result = 4 == corners && first == last;
387 if (result && rect) {
388 *rect = getBounds();
389 }
390 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391}
392
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000393int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394 SkDEBUGCODE(this->validate();)
395
396 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000397 SkASSERT(!max || dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 int count = fPts.count();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000399 fPts.copyRange(dst, 0, max);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000400 return count;
401}
402
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000403SkPoint SkPath::getPoint(int index) const {
404 if ((unsigned)index < (unsigned)fPts.count()) {
405 return fPts[index];
406 }
407 return SkPoint::Make(0, 0);
408}
409
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000410int SkPath::getVerbs(uint8_t dst[], int max) const {
411 SkDEBUGCODE(this->validate();)
412
413 SkASSERT(max >= 0);
414 SkASSERT(!max || dst);
415 fVerbs.copyRange(dst, 0, max);
416 return fVerbs.count();
417}
418
reed@google.com294dd7b2011-10-11 11:58:32 +0000419bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420 SkDEBUGCODE(this->validate();)
421
reed@google.com294dd7b2011-10-11 11:58:32 +0000422 int count = fPts.count();
423 if (count > 0) {
424 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 *lastPt = fPts[count - 1];
426 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000427 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000429 if (lastPt) {
430 lastPt->set(0, 0);
431 }
432 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433}
434
435void SkPath::setLastPt(SkScalar x, SkScalar y) {
436 SkDEBUGCODE(this->validate();)
437
438 int count = fPts.count();
439 if (count == 0) {
440 this->moveTo(x, y);
441 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000442 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000444 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445 }
446}
447
reed@android.comd252db02009-04-01 18:31:44 +0000448void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000450 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451
reed@android.comd252db02009-04-01 18:31:44 +0000452 fBoundsIsDirty = false;
453 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000454}
455
reed@google.com04863fa2011-05-15 04:08:24 +0000456void SkPath::setConvexity(Convexity c) {
457 if (fConvexity != c) {
458 fConvexity = c;
459 GEN_ID_INC;
460 }
461}
462
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463//////////////////////////////////////////////////////////////////////////////
464// Construction methods
465
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000466#define DIRTY_AFTER_EDIT \
467 do { \
468 fBoundsIsDirty = true; \
469 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000470 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000471 } while (0)
472
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000473#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
474 do { \
475 fBoundsIsDirty = true; \
476 } while (0)
477
reed@android.com8a1c16f2008-12-17 15:59:43 +0000478void SkPath::incReserve(U16CPU inc) {
479 SkDEBUGCODE(this->validate();)
480
481 fVerbs.setReserve(fVerbs.count() + inc);
482 fPts.setReserve(fPts.count() + inc);
483
484 SkDEBUGCODE(this->validate();)
485}
486
487void SkPath::moveTo(SkScalar x, SkScalar y) {
488 SkDEBUGCODE(this->validate();)
489
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490 SkPoint* pt;
491
reed@google.comd335d1d2012-01-12 18:17:11 +0000492 // remember our index
493 fLastMoveToIndex = fPts.count();
494
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000495 pt = fPts.append();
496 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000497 pt->set(x, y);
498
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000499 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000500 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000501}
502
503void SkPath::rMoveTo(SkScalar x, SkScalar y) {
504 SkPoint pt;
505 this->getLastPt(&pt);
506 this->moveTo(pt.fX + x, pt.fY + y);
507}
508
reed@google.comd335d1d2012-01-12 18:17:11 +0000509void SkPath::injectMoveToIfNeeded() {
510 if (fLastMoveToIndex < 0) {
511 SkScalar x, y;
512 if (fVerbs.count() == 0) {
513 x = y = 0;
514 } else {
515 const SkPoint& pt = fPts[~fLastMoveToIndex];
516 x = pt.fX;
517 y = pt.fY;
518 }
519 this->moveTo(x, y);
520 }
521}
522
reed@android.com8a1c16f2008-12-17 15:59:43 +0000523void SkPath::lineTo(SkScalar x, SkScalar y) {
524 SkDEBUGCODE(this->validate();)
525
reed@google.comd335d1d2012-01-12 18:17:11 +0000526 this->injectMoveToIfNeeded();
527
reed@android.com8a1c16f2008-12-17 15:59:43 +0000528 fPts.append()->set(x, y);
529 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000530 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000532 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000533 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534}
535
536void SkPath::rLineTo(SkScalar x, SkScalar y) {
537 SkPoint pt;
538 this->getLastPt(&pt);
539 this->lineTo(pt.fX + x, pt.fY + y);
540}
541
542void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
543 SkDEBUGCODE(this->validate();)
544
reed@google.comd335d1d2012-01-12 18:17:11 +0000545 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546
547 SkPoint* pts = fPts.append(2);
548 pts[0].set(x1, y1);
549 pts[1].set(x2, y2);
550 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000551 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000553 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000554 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555}
556
557void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
558 SkPoint pt;
559 this->getLastPt(&pt);
560 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
561}
562
563void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
564 SkScalar x3, SkScalar y3) {
565 SkDEBUGCODE(this->validate();)
566
reed@google.comd335d1d2012-01-12 18:17:11 +0000567 this->injectMoveToIfNeeded();
568
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 SkPoint* pts = fPts.append(3);
570 pts[0].set(x1, y1);
571 pts[1].set(x2, y2);
572 pts[2].set(x3, y3);
573 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000574 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000576 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000577 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578}
579
580void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
581 SkScalar x3, SkScalar y3) {
582 SkPoint pt;
583 this->getLastPt(&pt);
584 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
585 pt.fX + x3, pt.fY + y3);
586}
587
588void SkPath::close() {
589 SkDEBUGCODE(this->validate();)
590
591 int count = fVerbs.count();
592 if (count > 0) {
593 switch (fVerbs[count - 1]) {
594 case kLine_Verb:
595 case kQuad_Verb:
596 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000597 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000598 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000599 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 break;
601 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000602 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 break;
604 }
605 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000606
607 // signal that we need a moveTo to follow us (unless we're done)
608#if 0
609 if (fLastMoveToIndex >= 0) {
610 fLastMoveToIndex = ~fLastMoveToIndex;
611 }
612#else
613 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
614#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615}
616
617///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000618
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619void SkPath::addRect(const SkRect& rect, Direction dir) {
620 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
621}
622
623void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
624 SkScalar bottom, Direction dir) {
625 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
626
627 this->incReserve(5);
628
629 this->moveTo(left, top);
630 if (dir == kCCW_Direction) {
631 this->lineTo(left, bottom);
632 this->lineTo(right, bottom);
633 this->lineTo(right, top);
634 } else {
635 this->lineTo(right, top);
636 this->lineTo(right, bottom);
637 this->lineTo(left, bottom);
638 }
639 this->close();
640}
641
reed@google.com744faba2012-05-29 19:54:52 +0000642void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
643 SkDEBUGCODE(this->validate();)
644 if (count <= 0) {
645 return;
646 }
647
648 fLastMoveToIndex = fPts.count();
649 fPts.append(count, pts);
650
651 // +close makes room for the extra kClose_Verb
652 uint8_t* vb = fVerbs.append(count + close);
653 vb[0] = kMove_Verb;
654
655 if (count > 1) {
656 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
657 // be 0, the compiler will remove the test/branch entirely.
658 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
659 memset(&vb[1], kLine_Verb, count - 1);
660 } else {
661 for (int i = 1; i < count; ++i) {
662 vb[i] = kLine_Verb;
663 }
664 }
665 fSegmentMask |= kLine_SegmentMask;
666 }
667 if (close) {
668 vb[count] = kClose_Verb;
669 }
670
671 GEN_ID_INC;
672 DIRTY_AFTER_EDIT;
673}
674
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
676
677void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
678 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679 SkScalar w = rect.width();
680 SkScalar halfW = SkScalarHalf(w);
681 SkScalar h = rect.height();
682 SkScalar halfH = SkScalarHalf(h);
683
684 if (halfW <= 0 || halfH <= 0) {
685 return;
686 }
687
reed@google.comabf15c12011-01-18 20:35:51 +0000688 bool skip_hori = rx >= halfW;
689 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690
691 if (skip_hori && skip_vert) {
692 this->addOval(rect, dir);
693 return;
694 }
reed@google.comabf15c12011-01-18 20:35:51 +0000695
696 SkAutoPathBoundsUpdate apbu(this, rect);
697
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 if (skip_hori) {
699 rx = halfW;
700 } else if (skip_vert) {
701 ry = halfH;
702 }
703
704 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
705 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
706
707 this->incReserve(17);
708 this->moveTo(rect.fRight - rx, rect.fTop);
709 if (dir == kCCW_Direction) {
710 if (!skip_hori) {
711 this->lineTo(rect.fLeft + rx, rect.fTop); // top
712 }
713 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
714 rect.fLeft, rect.fTop + ry - sy,
715 rect.fLeft, rect.fTop + ry); // top-left
716 if (!skip_vert) {
717 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
718 }
719 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
720 rect.fLeft + rx - sx, rect.fBottom,
721 rect.fLeft + rx, rect.fBottom); // bot-left
722 if (!skip_hori) {
723 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
724 }
725 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
726 rect.fRight, rect.fBottom - ry + sy,
727 rect.fRight, rect.fBottom - ry); // bot-right
728 if (!skip_vert) {
729 this->lineTo(rect.fRight, rect.fTop + ry);
730 }
731 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
732 rect.fRight - rx + sx, rect.fTop,
733 rect.fRight - rx, rect.fTop); // top-right
734 } else {
735 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
736 rect.fRight, rect.fTop + ry - sy,
737 rect.fRight, rect.fTop + ry); // top-right
738 if (!skip_vert) {
739 this->lineTo(rect.fRight, rect.fBottom - ry);
740 }
741 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
742 rect.fRight - rx + sx, rect.fBottom,
743 rect.fRight - rx, rect.fBottom); // bot-right
744 if (!skip_hori) {
745 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
746 }
747 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
748 rect.fLeft, rect.fBottom - ry + sy,
749 rect.fLeft, rect.fBottom - ry); // bot-left
750 if (!skip_vert) {
751 this->lineTo(rect.fLeft, rect.fTop + ry); // left
752 }
753 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
754 rect.fLeft + rx - sx, rect.fTop,
755 rect.fLeft + rx, rect.fTop); // top-left
756 if (!skip_hori) {
757 this->lineTo(rect.fRight - rx, rect.fTop); // top
758 }
759 }
760 this->close();
761}
762
763static void add_corner_arc(SkPath* path, const SkRect& rect,
764 SkScalar rx, SkScalar ry, int startAngle,
765 SkPath::Direction dir, bool forceMoveTo) {
766 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
767 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000768
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 SkRect r;
770 r.set(-rx, -ry, rx, ry);
771
772 switch (startAngle) {
773 case 0:
774 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
775 break;
776 case 90:
777 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
778 break;
779 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
780 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000781 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 }
reed@google.comabf15c12011-01-18 20:35:51 +0000783
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 SkScalar start = SkIntToScalar(startAngle);
785 SkScalar sweep = SkIntToScalar(90);
786 if (SkPath::kCCW_Direction == dir) {
787 start += sweep;
788 sweep = -sweep;
789 }
reed@google.comabf15c12011-01-18 20:35:51 +0000790
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 path->arcTo(r, start, sweep, forceMoveTo);
792}
793
794void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
795 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000796 // abort before we invoke SkAutoPathBoundsUpdate()
797 if (rect.isEmpty()) {
798 return;
799 }
800
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 SkAutoPathBoundsUpdate apbu(this, rect);
802
803 if (kCW_Direction == dir) {
804 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
805 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
806 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
807 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
808 } else {
809 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
810 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
811 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
812 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
813 }
814 this->close();
815}
816
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000817bool SkPath::hasOnlyMoveTos() const {
818 const uint8_t* verbs = fVerbs.begin();
819 const uint8_t* verbStop = fVerbs.end();
820 while (verbs != verbStop) {
821 if (*verbs == kLine_Verb ||
822 *verbs == kQuad_Verb ||
823 *verbs == kCubic_Verb) {
824 return false;
825 }
826 ++verbs;
827 }
828 return true;
829}
830
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000832 /* If addOval() is called after previous moveTo(),
833 this path is still marked as an oval. This is used to
834 fit into WebKit's calling sequences.
835 We can't simply check isEmpty() in this case, as additional
836 moveTo() would mark the path non empty.
837 */
838 fIsOval = hasOnlyMoveTos();
839
840 SkAutoDisableOvalCheck adoc(this);
841
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 SkAutoPathBoundsUpdate apbu(this, oval);
843
844 SkScalar cx = oval.centerX();
845 SkScalar cy = oval.centerY();
846 SkScalar rx = SkScalarHalf(oval.width());
847 SkScalar ry = SkScalarHalf(oval.height());
848#if 0 // these seem faster than using quads (1/2 the number of edges)
849 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
850 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
851
852 this->incReserve(13);
853 this->moveTo(cx + rx, cy);
854 if (dir == kCCW_Direction) {
855 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
856 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
857 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
858 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
859 } else {
860 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
861 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
862 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
863 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
864 }
865#else
866 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
867 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
868 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
869 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
870
871 /*
872 To handle imprecision in computing the center and radii, we revert to
873 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
874 to ensure that we don't exceed the oval's bounds *ever*, since we want
875 to use oval for our fast-bounds, rather than have to recompute it.
876 */
877 const SkScalar L = oval.fLeft; // cx - rx
878 const SkScalar T = oval.fTop; // cy - ry
879 const SkScalar R = oval.fRight; // cx + rx
880 const SkScalar B = oval.fBottom; // cy + ry
881
882 this->incReserve(17); // 8 quads + close
883 this->moveTo(R, cy);
884 if (dir == kCCW_Direction) {
885 this->quadTo( R, cy - sy, cx + mx, cy - my);
886 this->quadTo(cx + sx, T, cx , T);
887 this->quadTo(cx - sx, T, cx - mx, cy - my);
888 this->quadTo( L, cy - sy, L, cy );
889 this->quadTo( L, cy + sy, cx - mx, cy + my);
890 this->quadTo(cx - sx, B, cx , B);
891 this->quadTo(cx + sx, B, cx + mx, cy + my);
892 this->quadTo( R, cy + sy, R, cy );
893 } else {
894 this->quadTo( R, cy + sy, cx + mx, cy + my);
895 this->quadTo(cx + sx, B, cx , B);
896 this->quadTo(cx - sx, B, cx - mx, cy + my);
897 this->quadTo( L, cy + sy, L, cy );
898 this->quadTo( L, cy - sy, cx - mx, cy - my);
899 this->quadTo(cx - sx, T, cx , T);
900 this->quadTo(cx + sx, T, cx + mx, cy - my);
901 this->quadTo( R, cy - sy, R, cy );
902 }
903#endif
904 this->close();
905}
906
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000907bool SkPath::isOval(SkRect* rect) const {
908 if (fIsOval && rect) {
909 *rect = getBounds();
910 }
911
912 return fIsOval;
913}
914
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
916 if (r > 0) {
917 SkRect rect;
918 rect.set(x - r, y - r, x + r, y + r);
919 this->addOval(rect, dir);
920 }
921}
922
923#include "SkGeometry.h"
924
925static int build_arc_points(const SkRect& oval, SkScalar startAngle,
926 SkScalar sweepAngle,
927 SkPoint pts[kSkBuildQuadArcStorage]) {
928 SkVector start, stop;
929
930 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
931 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
932 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000933
934 /* If the sweep angle is nearly (but less than) 360, then due to precision
935 loss in radians-conversion and/or sin/cos, we may end up with coincident
936 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
937 of drawing a nearly complete circle (good).
938 e.g. canvas.drawArc(0, 359.99, ...)
939 -vs- canvas.drawArc(0, 359.9, ...)
940 We try to detect this edge case, and tweak the stop vector
941 */
942 if (start == stop) {
943 SkScalar sw = SkScalarAbs(sweepAngle);
944 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
945 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
946 // make a guess at a tiny angle (in radians) to tweak by
947 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
948 // not sure how much will be enough, so we use a loop
949 do {
950 stopRad -= deltaRad;
951 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
952 } while (start == stop);
953 }
954 }
955
reed@android.com8a1c16f2008-12-17 15:59:43 +0000956 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000957
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
959 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000960
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 return SkBuildQuadArc(start, stop,
962 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
963 &matrix, pts);
964}
965
966void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
967 bool forceMoveTo) {
968 if (oval.width() < 0 || oval.height() < 0) {
969 return;
970 }
971
972 SkPoint pts[kSkBuildQuadArcStorage];
973 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
974 SkASSERT((count & 1) == 1);
975
976 if (fVerbs.count() == 0) {
977 forceMoveTo = true;
978 }
979 this->incReserve(count);
980 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
981 for (int i = 1; i < count; i += 2) {
982 this->quadTo(pts[i], pts[i+1]);
983 }
984}
985
986void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
987 SkScalar sweepAngle) {
988 if (oval.isEmpty() || 0 == sweepAngle) {
989 return;
990 }
991
992 const SkScalar kFullCircleAngle = SkIntToScalar(360);
993
994 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
995 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
996 return;
997 }
998
999 SkPoint pts[kSkBuildQuadArcStorage];
1000 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1001
1002 this->incReserve(count);
1003 this->moveTo(pts[0]);
1004 for (int i = 1; i < count; i += 2) {
1005 this->quadTo(pts[i], pts[i+1]);
1006 }
1007}
1008
1009/*
1010 Need to handle the case when the angle is sharp, and our computed end-points
1011 for the arc go behind pt1 and/or p2...
1012*/
1013void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1014 SkScalar radius) {
1015 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001016
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 // need to know our prev pt so we can construct tangent vectors
1018 {
1019 SkPoint start;
1020 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001021 // Handle degenerate cases by adding a line to the first point and
1022 // bailing out.
1023 if ((x1 == start.fX && y1 == start.fY) ||
1024 (x1 == x2 && y1 == y2) ||
1025 radius == 0) {
1026 this->lineTo(x1, y1);
1027 return;
1028 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 before.setNormalize(x1 - start.fX, y1 - start.fY);
1030 after.setNormalize(x2 - x1, y2 - y1);
1031 }
reed@google.comabf15c12011-01-18 20:35:51 +00001032
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033 SkScalar cosh = SkPoint::DotProduct(before, after);
1034 SkScalar sinh = SkPoint::CrossProduct(before, after);
1035
1036 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001037 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001038 return;
1039 }
reed@google.comabf15c12011-01-18 20:35:51 +00001040
reed@android.com8a1c16f2008-12-17 15:59:43 +00001041 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1042 if (dist < 0) {
1043 dist = -dist;
1044 }
1045
1046 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1047 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1048 SkRotationDirection arcDir;
1049
1050 // now turn before/after into normals
1051 if (sinh > 0) {
1052 before.rotateCCW();
1053 after.rotateCCW();
1054 arcDir = kCW_SkRotationDirection;
1055 } else {
1056 before.rotateCW();
1057 after.rotateCW();
1058 arcDir = kCCW_SkRotationDirection;
1059 }
1060
1061 SkMatrix matrix;
1062 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001063
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 matrix.setScale(radius, radius);
1065 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1066 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001067
reed@android.com8a1c16f2008-12-17 15:59:43 +00001068 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001069
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 this->incReserve(count);
1071 // [xx,yy] == pts[0]
1072 this->lineTo(xx, yy);
1073 for (int i = 1; i < count; i += 2) {
1074 this->quadTo(pts[i], pts[i+1]);
1075 }
1076}
1077
1078///////////////////////////////////////////////////////////////////////////////
1079
1080void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1081 SkMatrix matrix;
1082
1083 matrix.setTranslate(dx, dy);
1084 this->addPath(path, matrix);
1085}
1086
1087void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1088 this->incReserve(path.fPts.count());
1089
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001090 fIsOval = false;
1091
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001092 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093 SkPoint pts[4];
1094 Verb verb;
1095
1096 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1097
1098 while ((verb = iter.next(pts)) != kDone_Verb) {
1099 switch (verb) {
1100 case kMove_Verb:
1101 proc(matrix, &pts[0], &pts[0], 1);
1102 this->moveTo(pts[0]);
1103 break;
1104 case kLine_Verb:
1105 proc(matrix, &pts[1], &pts[1], 1);
1106 this->lineTo(pts[1]);
1107 break;
1108 case kQuad_Verb:
1109 proc(matrix, &pts[1], &pts[1], 2);
1110 this->quadTo(pts[1], pts[2]);
1111 break;
1112 case kCubic_Verb:
1113 proc(matrix, &pts[1], &pts[1], 3);
1114 this->cubicTo(pts[1], pts[2], pts[3]);
1115 break;
1116 case kClose_Verb:
1117 this->close();
1118 break;
1119 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001120 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121 }
1122 }
1123}
1124
1125///////////////////////////////////////////////////////////////////////////////
1126
1127static const uint8_t gPtsInVerb[] = {
1128 1, // kMove
1129 1, // kLine
1130 2, // kQuad
1131 3, // kCubic
1132 0, // kClose
1133 0 // kDone
1134};
1135
1136// ignore the initial moveto, and stop when the 1st contour ends
1137void SkPath::pathTo(const SkPath& path) {
1138 int i, vcount = path.fVerbs.count();
1139 if (vcount == 0) {
1140 return;
1141 }
1142
1143 this->incReserve(vcount);
1144
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001145 fIsOval = false;
1146
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 const uint8_t* verbs = path.fVerbs.begin();
1148 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1149
1150 SkASSERT(verbs[0] == kMove_Verb);
1151 for (i = 1; i < vcount; i++) {
1152 switch (verbs[i]) {
1153 case kLine_Verb:
1154 this->lineTo(pts[0].fX, pts[0].fY);
1155 break;
1156 case kQuad_Verb:
1157 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1158 break;
1159 case kCubic_Verb:
1160 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1161 pts[2].fX, pts[2].fY);
1162 break;
1163 case kClose_Verb:
1164 return;
1165 }
1166 pts += gPtsInVerb[verbs[i]];
1167 }
1168}
1169
1170// ignore the last point of the 1st contour
1171void SkPath::reversePathTo(const SkPath& path) {
1172 int i, vcount = path.fVerbs.count();
1173 if (vcount == 0) {
1174 return;
1175 }
1176
1177 this->incReserve(vcount);
1178
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001179 fIsOval = false;
1180
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 const uint8_t* verbs = path.fVerbs.begin();
1182 const SkPoint* pts = path.fPts.begin();
1183
1184 SkASSERT(verbs[0] == kMove_Verb);
1185 for (i = 1; i < vcount; i++) {
1186 int n = gPtsInVerb[verbs[i]];
1187 if (n == 0) {
1188 break;
1189 }
1190 pts += n;
1191 }
1192
1193 while (--i > 0) {
1194 switch (verbs[i]) {
1195 case kLine_Verb:
1196 this->lineTo(pts[-1].fX, pts[-1].fY);
1197 break;
1198 case kQuad_Verb:
1199 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1200 break;
1201 case kCubic_Verb:
1202 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1203 pts[-3].fX, pts[-3].fY);
1204 break;
1205 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001206 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 break;
1208 }
1209 pts -= gPtsInVerb[verbs[i]];
1210 }
1211}
1212
reed@google.com63d73742012-01-10 15:33:12 +00001213void SkPath::reverseAddPath(const SkPath& src) {
1214 this->incReserve(src.fPts.count());
1215
reed@google.com63d73742012-01-10 15:33:12 +00001216 const SkPoint* pts = src.fPts.end();
1217 const uint8_t* startVerbs = src.fVerbs.begin();
1218 const uint8_t* verbs = src.fVerbs.end();
1219
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001220 fIsOval = false;
1221
reed@google.com63d73742012-01-10 15:33:12 +00001222 bool needMove = true;
1223 bool needClose = false;
1224 while (verbs > startVerbs) {
1225 uint8_t v = *--verbs;
1226 int n = gPtsInVerb[v];
1227
1228 if (needMove) {
1229 --pts;
1230 this->moveTo(pts->fX, pts->fY);
1231 needMove = false;
1232 }
1233 pts -= n;
1234 switch (v) {
1235 case kMove_Verb:
1236 if (needClose) {
1237 this->close();
1238 needClose = false;
1239 }
1240 needMove = true;
1241 pts += 1; // so we see the point in "if (needMove)" above
1242 break;
1243 case kLine_Verb:
1244 this->lineTo(pts[0]);
1245 break;
1246 case kQuad_Verb:
1247 this->quadTo(pts[1], pts[0]);
1248 break;
1249 case kCubic_Verb:
1250 this->cubicTo(pts[2], pts[1], pts[0]);
1251 break;
1252 case kClose_Verb:
1253 needClose = true;
1254 break;
1255 default:
1256 SkASSERT(!"unexpected verb");
1257 }
1258 }
1259}
1260
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261///////////////////////////////////////////////////////////////////////////////
1262
1263void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1264 SkMatrix matrix;
1265
1266 matrix.setTranslate(dx, dy);
1267 this->transform(matrix, dst);
1268}
1269
1270#include "SkGeometry.h"
1271
1272static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1273 int level = 2) {
1274 if (--level >= 0) {
1275 SkPoint tmp[5];
1276
1277 SkChopQuadAtHalf(pts, tmp);
1278 subdivide_quad_to(path, &tmp[0], level);
1279 subdivide_quad_to(path, &tmp[2], level);
1280 } else {
1281 path->quadTo(pts[1], pts[2]);
1282 }
1283}
1284
1285static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1286 int level = 2) {
1287 if (--level >= 0) {
1288 SkPoint tmp[7];
1289
1290 SkChopCubicAtHalf(pts, tmp);
1291 subdivide_cubic_to(path, &tmp[0], level);
1292 subdivide_cubic_to(path, &tmp[3], level);
1293 } else {
1294 path->cubicTo(pts[1], pts[2], pts[3]);
1295 }
1296}
1297
1298void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1299 SkDEBUGCODE(this->validate();)
1300 if (dst == NULL) {
1301 dst = (SkPath*)this;
1302 }
1303
tomhudson@google.com8d430182011-06-06 19:11:19 +00001304 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 SkPath tmp;
1306 tmp.fFillType = fFillType;
1307
1308 SkPath::Iter iter(*this, false);
1309 SkPoint pts[4];
1310 SkPath::Verb verb;
1311
reed@google.com4a3b7142012-05-16 17:16:46 +00001312 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 switch (verb) {
1314 case kMove_Verb:
1315 tmp.moveTo(pts[0]);
1316 break;
1317 case kLine_Verb:
1318 tmp.lineTo(pts[1]);
1319 break;
1320 case kQuad_Verb:
1321 subdivide_quad_to(&tmp, pts);
1322 break;
1323 case kCubic_Verb:
1324 subdivide_cubic_to(&tmp, pts);
1325 break;
1326 case kClose_Verb:
1327 tmp.close();
1328 break;
1329 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001330 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 break;
1332 }
1333 }
1334
1335 dst->swap(tmp);
1336 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1337 } else {
1338 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001339 // fBoundsIsDirty before we set it
1340 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001342 matrix.mapRect(&dst->fBounds, fBounds);
1343 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001345 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001346 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 }
1348
1349 if (this != dst) {
1350 dst->fVerbs = fVerbs;
1351 dst->fPts.setCount(fPts.count());
1352 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001353 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001354 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001355 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001357
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001359
1360 if (fIsOval) {
1361 // It's an oval only if it stays a rect.
1362 dst->fIsOval = matrix.rectStaysRect();
1363 }
1364
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 SkDEBUGCODE(dst->validate();)
1366 }
1367}
1368
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369///////////////////////////////////////////////////////////////////////////////
1370///////////////////////////////////////////////////////////////////////////////
1371
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001372enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001373 kEmptyContour_SegmentState, // The current contour is empty. We may be
1374 // starting processing or we may have just
1375 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001376 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1377 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1378 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379};
1380
1381SkPath::Iter::Iter() {
1382#ifdef SK_DEBUG
1383 fPts = NULL;
1384 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001385 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001386 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387#endif
1388 // need to init enough to make next() harmlessly return kDone_Verb
1389 fVerbs = NULL;
1390 fVerbStop = NULL;
1391 fNeedClose = false;
1392}
1393
1394SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1395 this->setPath(path, forceClose);
1396}
1397
1398void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1399 fPts = path.fPts.begin();
1400 fVerbs = path.fVerbs.begin();
1401 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001402 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001403 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 fForceClose = SkToU8(forceClose);
1405 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001406 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407}
1408
1409bool SkPath::Iter::isClosedContour() const {
1410 if (fVerbs == NULL || fVerbs == fVerbStop) {
1411 return false;
1412 }
1413 if (fForceClose) {
1414 return true;
1415 }
1416
1417 const uint8_t* verbs = fVerbs;
1418 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001419
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 if (kMove_Verb == *verbs) {
1421 verbs += 1; // skip the initial moveto
1422 }
1423
1424 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001425 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 if (kMove_Verb == v) {
1427 break;
1428 }
1429 if (kClose_Verb == v) {
1430 return true;
1431 }
1432 }
1433 return false;
1434}
1435
1436SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001437 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001439 // A special case: if both points are NaN, SkPoint::operation== returns
1440 // false, but the iterator expects that they are treated as the same.
1441 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001442 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1443 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001444 return kClose_Verb;
1445 }
1446
reed@google.com9e25dbf2012-05-15 17:05:38 +00001447 pts[0] = fLastPt;
1448 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 fLastPt = fMoveTo;
1450 fCloseLine = true;
1451 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001452 } else {
1453 pts[0] = fMoveTo;
1454 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456}
1457
reed@google.com9e25dbf2012-05-15 17:05:38 +00001458const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001459 if (fSegmentState == kAfterMove_SegmentState) {
1460 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001461 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001462 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001464 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1465 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001466 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468}
1469
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001470void SkPath::Iter::consumeDegenerateSegments() {
1471 // We need to step over anything that will not move the current draw point
1472 // forward before the next move is seen
1473 const uint8_t* lastMoveVerb = 0;
1474 const SkPoint* lastMovePt = 0;
1475 SkPoint lastPt = fLastPt;
1476 while (fVerbs != fVerbStop) {
1477 unsigned verb = *fVerbs;
1478 switch (verb) {
1479 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001480 // Keep a record of this most recent move
1481 lastMoveVerb = fVerbs;
1482 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001483 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001484 fVerbs++;
1485 fPts++;
1486 break;
1487
1488 case kClose_Verb:
1489 // A close when we are in a segment is always valid
1490 if (fSegmentState == kAfterPrimitive_SegmentState) {
1491 return;
1492 }
1493 // A close at any other time must be ignored
1494 fVerbs++;
1495 break;
1496
1497 case kLine_Verb:
1498 if (!IsLineDegenerate(lastPt, fPts[0])) {
1499 if (lastMoveVerb) {
1500 fVerbs = lastMoveVerb;
1501 fPts = lastMovePt;
1502 return;
1503 }
1504 return;
1505 }
1506 // Ignore this line and continue
1507 fVerbs++;
1508 fPts++;
1509 break;
1510
1511 case kQuad_Verb:
1512 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1513 if (lastMoveVerb) {
1514 fVerbs = lastMoveVerb;
1515 fPts = lastMovePt;
1516 return;
1517 }
1518 return;
1519 }
1520 // Ignore this line and continue
1521 fVerbs++;
1522 fPts += 2;
1523 break;
1524
1525 case kCubic_Verb:
1526 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1527 if (lastMoveVerb) {
1528 fVerbs = lastMoveVerb;
1529 fPts = lastMovePt;
1530 return;
1531 }
1532 return;
1533 }
1534 // Ignore this line and continue
1535 fVerbs++;
1536 fPts += 3;
1537 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001538
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001539 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001540 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001541 }
1542 }
1543}
1544
reed@google.com4a3b7142012-05-16 17:16:46 +00001545SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001546 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001547
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001549 // Close the curve if requested and if there is some curve to close
1550 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001551 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 return kLine_Verb;
1553 }
1554 fNeedClose = false;
1555 return kClose_Verb;
1556 }
1557 return kDone_Verb;
1558 }
1559
1560 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001561 const SkPoint* SK_RESTRICT srcPts = fPts;
1562 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563
1564 switch (verb) {
1565 case kMove_Verb:
1566 if (fNeedClose) {
1567 fVerbs -= 1;
1568 verb = this->autoClose(pts);
1569 if (verb == kClose_Verb) {
1570 fNeedClose = false;
1571 }
1572 return (Verb)verb;
1573 }
1574 if (fVerbs == fVerbStop) { // might be a trailing moveto
1575 return kDone_Verb;
1576 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001577 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001578 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001580 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001581 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001582 fNeedClose = fForceClose;
1583 break;
1584 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001585 pts[0] = this->cons_moveTo();
1586 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 fLastPt = srcPts[0];
1588 fCloseLine = false;
1589 srcPts += 1;
1590 break;
1591 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001592 pts[0] = this->cons_moveTo();
1593 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 fLastPt = srcPts[1];
1595 srcPts += 2;
1596 break;
1597 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001598 pts[0] = this->cons_moveTo();
1599 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 fLastPt = srcPts[2];
1601 srcPts += 3;
1602 break;
1603 case kClose_Verb:
1604 verb = this->autoClose(pts);
1605 if (verb == kLine_Verb) {
1606 fVerbs -= 1;
1607 } else {
1608 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001609 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001611 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 break;
1613 }
1614 fPts = srcPts;
1615 return (Verb)verb;
1616}
1617
1618///////////////////////////////////////////////////////////////////////////////
1619
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001620SkPath::RawIter::RawIter() {
1621#ifdef SK_DEBUG
1622 fPts = NULL;
1623 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1624#endif
1625 // need to init enough to make next() harmlessly return kDone_Verb
1626 fVerbs = NULL;
1627 fVerbStop = NULL;
1628}
1629
1630SkPath::RawIter::RawIter(const SkPath& path) {
1631 this->setPath(path);
1632}
1633
1634void SkPath::RawIter::setPath(const SkPath& path) {
1635 fPts = path.fPts.begin();
1636 fVerbs = path.fVerbs.begin();
1637 fVerbStop = path.fVerbs.end();
1638 fMoveTo.fX = fMoveTo.fY = 0;
1639 fLastPt.fX = fLastPt.fY = 0;
1640}
1641
1642SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001643 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001644 if (fVerbs == fVerbStop) {
1645 return kDone_Verb;
1646 }
1647
1648 unsigned verb = *fVerbs++;
1649 const SkPoint* srcPts = fPts;
1650
1651 switch (verb) {
1652 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001653 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001654 fMoveTo = srcPts[0];
1655 fLastPt = fMoveTo;
1656 srcPts += 1;
1657 break;
1658 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001659 pts[0] = fLastPt;
1660 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001661 fLastPt = srcPts[0];
1662 srcPts += 1;
1663 break;
1664 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001665 pts[0] = fLastPt;
1666 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001667 fLastPt = srcPts[1];
1668 srcPts += 2;
1669 break;
1670 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001671 pts[0] = fLastPt;
1672 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001673 fLastPt = srcPts[2];
1674 srcPts += 3;
1675 break;
1676 case kClose_Verb:
1677 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001678 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001679 break;
1680 }
1681 fPts = srcPts;
1682 return (Verb)verb;
1683}
1684
1685///////////////////////////////////////////////////////////////////////////////
1686
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687/*
1688 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1689*/
1690
reed@google.com73945652011-04-25 19:04:27 +00001691void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 SkDEBUGCODE(this->validate();)
1693
1694 buffer.write32(fPts.count());
1695 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001696 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1698 buffer.writePad(fVerbs.begin(), fVerbs.count());
1699}
1700
reed@google.com73945652011-04-25 19:04:27 +00001701void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 fPts.setCount(buffer.readS32());
1703 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001704 uint32_t packed = buffer.readS32();
1705 fFillType = packed >> 8;
1706 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1708 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001709
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001710 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001711 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712
1713 SkDEBUGCODE(this->validate();)
1714}
1715
1716///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718void SkPath::dump(bool forceClose, const char title[]) const {
1719 Iter iter(*this, forceClose);
1720 SkPoint pts[4];
1721 Verb verb;
1722
1723 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1724 title ? title : "");
1725
reed@google.com4a3b7142012-05-16 17:16:46 +00001726 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 switch (verb) {
1728 case kMove_Verb:
1729#ifdef SK_CAN_USE_FLOAT
1730 SkDebugf(" path: moveTo [%g %g]\n",
1731 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1732#else
1733 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1734#endif
1735 break;
1736 case kLine_Verb:
1737#ifdef SK_CAN_USE_FLOAT
1738 SkDebugf(" path: lineTo [%g %g]\n",
1739 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1740#else
1741 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1742#endif
1743 break;
1744 case kQuad_Verb:
1745#ifdef SK_CAN_USE_FLOAT
1746 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1747 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1748 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1749#else
1750 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1751 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1752#endif
1753 break;
1754 case kCubic_Verb:
1755#ifdef SK_CAN_USE_FLOAT
1756 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1757 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1758 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1759 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1760#else
1761 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1762 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1763 pts[3].fX, pts[3].fY);
1764#endif
1765 break;
1766 case kClose_Verb:
1767 SkDebugf(" path: close\n");
1768 break;
1769 default:
1770 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1771 verb = kDone_Verb; // stop the loop
1772 break;
1773 }
1774 }
1775 SkDebugf("path: done %s\n", title ? title : "");
1776}
1777
reed@android.come522ca52009-11-23 20:10:41 +00001778void SkPath::dump() const {
1779 this->dump(false);
1780}
1781
1782#ifdef SK_DEBUG
1783void SkPath::validate() const {
1784 SkASSERT(this != NULL);
1785 SkASSERT((fFillType & ~3) == 0);
1786 fPts.validate();
1787 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001788
reed@android.come522ca52009-11-23 20:10:41 +00001789 if (!fBoundsIsDirty) {
1790 SkRect bounds;
1791 compute_pt_bounds(&bounds, fPts);
1792 if (fPts.count() <= 1) {
1793 // if we're empty, fBounds may be empty but translated, so we can't
1794 // necessarily compare to bounds directly
1795 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1796 // be [2, 2, 2, 2]
1797 SkASSERT(bounds.isEmpty());
1798 SkASSERT(fBounds.isEmpty());
1799 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001800 if (bounds.isEmpty()) {
1801 SkASSERT(fBounds.isEmpty());
1802 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001803 if (!fBounds.isEmpty()) {
1804 SkASSERT(fBounds.contains(bounds));
1805 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001806 }
reed@android.come522ca52009-11-23 20:10:41 +00001807 }
1808 }
reed@google.com10296cc2011-09-21 12:29:05 +00001809
1810 uint32_t mask = 0;
1811 for (int i = 0; i < fVerbs.count(); i++) {
1812 switch (fVerbs[i]) {
1813 case kLine_Verb:
1814 mask |= kLine_SegmentMask;
1815 break;
1816 case kQuad_Verb:
1817 mask |= kQuad_SegmentMask;
1818 break;
1819 case kCubic_Verb:
1820 mask |= kCubic_SegmentMask;
1821 }
1822 }
1823 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001824}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825#endif
reed@android.come522ca52009-11-23 20:10:41 +00001826
reed@google.com04863fa2011-05-15 04:08:24 +00001827///////////////////////////////////////////////////////////////////////////////
1828
reed@google.com0b7b9822011-05-16 12:29:27 +00001829static int sign(SkScalar x) { return x < 0; }
1830#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001831
reed@google.com04863fa2011-05-15 04:08:24 +00001832static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001833 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001834}
1835
1836// only valid for a single contour
1837struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001838 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001839 fSign = 0;
1840 // warnings
1841 fCurrPt.set(0, 0);
1842 fVec0.set(0, 0);
1843 fVec1.set(0, 0);
1844 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001845
1846 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001847 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001848 }
1849
1850 SkPath::Convexity getConvexity() const { return fConvexity; }
1851
1852 void addPt(const SkPoint& pt) {
1853 if (SkPath::kConcave_Convexity == fConvexity) {
1854 return;
1855 }
1856
1857 if (0 == fPtCount) {
1858 fCurrPt = pt;
1859 ++fPtCount;
1860 } else {
1861 SkVector vec = pt - fCurrPt;
1862 if (vec.fX || vec.fY) {
1863 fCurrPt = pt;
1864 if (++fPtCount == 2) {
1865 fFirstVec = fVec1 = vec;
1866 } else {
1867 SkASSERT(fPtCount > 2);
1868 this->addVec(vec);
1869 }
reed@google.com85b6e392011-05-15 20:25:17 +00001870
1871 int sx = sign(vec.fX);
1872 int sy = sign(vec.fY);
1873 fDx += (sx != fSx);
1874 fDy += (sy != fSy);
1875 fSx = sx;
1876 fSy = sy;
1877
1878 if (fDx > 3 || fDy > 3) {
1879 fConvexity = SkPath::kConcave_Convexity;
1880 }
reed@google.com04863fa2011-05-15 04:08:24 +00001881 }
1882 }
1883 }
1884
1885 void close() {
1886 if (fPtCount > 2) {
1887 this->addVec(fFirstVec);
1888 }
1889 }
1890
1891private:
1892 void addVec(const SkVector& vec) {
1893 SkASSERT(vec.fX || vec.fY);
1894 fVec0 = fVec1;
1895 fVec1 = vec;
1896 int sign = CrossProductSign(fVec0, fVec1);
1897 if (0 == fSign) {
1898 fSign = sign;
1899 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001900 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001901 fConvexity = SkPath::kConcave_Convexity;
1902 }
1903 }
1904 }
1905
1906 SkPoint fCurrPt;
1907 SkVector fVec0, fVec1, fFirstVec;
1908 int fPtCount; // non-degenerate points
1909 int fSign;
1910 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001911 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001912};
1913
1914SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1915 SkPoint pts[4];
1916 SkPath::Verb verb;
1917 SkPath::Iter iter(path, true);
1918
1919 int contourCount = 0;
1920 int count;
1921 Convexicator state;
1922
1923 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1924 switch (verb) {
1925 case kMove_Verb:
1926 if (++contourCount > 1) {
1927 return kConcave_Convexity;
1928 }
1929 pts[1] = pts[0];
1930 count = 1;
1931 break;
1932 case kLine_Verb: count = 1; break;
1933 case kQuad_Verb: count = 2; break;
1934 case kCubic_Verb: count = 3; break;
1935 case kClose_Verb:
1936 state.close();
1937 count = 0;
1938 break;
1939 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001940 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001941 return kConcave_Convexity;
1942 }
1943
1944 for (int i = 1; i <= count; i++) {
1945 state.addPt(pts[i]);
1946 }
1947 // early exit
1948 if (kConcave_Convexity == state.getConvexity()) {
1949 return kConcave_Convexity;
1950 }
1951 }
1952 return state.getConvexity();
1953}
reed@google.com69a99432012-01-10 18:00:10 +00001954
1955///////////////////////////////////////////////////////////////////////////////
1956
1957class ContourIter {
1958public:
1959 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1960
1961 bool done() const { return fDone; }
1962 // if !done() then these may be called
1963 int count() const { return fCurrPtCount; }
1964 const SkPoint* pts() const { return fCurrPt; }
1965 void next();
1966
1967private:
1968 int fCurrPtCount;
1969 const SkPoint* fCurrPt;
1970 const uint8_t* fCurrVerb;
1971 const uint8_t* fStopVerbs;
1972 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001973 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001974};
1975
1976ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1977 const SkTDArray<SkPoint>& pts) {
1978 fStopVerbs = verbs.begin() + verbs.count();
1979
1980 fDone = false;
1981 fCurrPt = pts.begin();
1982 fCurrVerb = verbs.begin();
1983 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001984 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001985 this->next();
1986}
1987
1988void ContourIter::next() {
1989 if (fCurrVerb >= fStopVerbs) {
1990 fDone = true;
1991 }
1992 if (fDone) {
1993 return;
1994 }
1995
1996 // skip pts of prev contour
1997 fCurrPt += fCurrPtCount;
1998
1999 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2000 int ptCount = 1; // moveTo
2001 const uint8_t* verbs = fCurrVerb;
2002
2003 for (++verbs; verbs < fStopVerbs; ++verbs) {
2004 switch (*verbs) {
2005 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002006 goto CONTOUR_END;
2007 case SkPath::kLine_Verb:
2008 ptCount += 1;
2009 break;
2010 case SkPath::kQuad_Verb:
2011 ptCount += 2;
2012 break;
2013 case SkPath::kCubic_Verb:
2014 ptCount += 3;
2015 break;
2016 default: // kClose_Verb, just keep going
2017 break;
2018 }
2019 }
2020CONTOUR_END:
2021 fCurrPtCount = ptCount;
2022 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002023 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002024}
2025
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002026// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002027static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002028 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2029 // We may get 0 when the above subtracts underflow. We expect this to be
2030 // very rare and lazily promote to double.
2031 if (0 == cross) {
2032 double p0x = SkScalarToDouble(p0.fX);
2033 double p0y = SkScalarToDouble(p0.fY);
2034
2035 double p1x = SkScalarToDouble(p1.fX);
2036 double p1y = SkScalarToDouble(p1.fY);
2037
2038 double p2x = SkScalarToDouble(p2.fX);
2039 double p2y = SkScalarToDouble(p2.fY);
2040
2041 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2042 (p1y - p0y) * (p2x - p0x));
2043
2044 }
2045 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002046}
2047
reed@google.comc1ea60a2012-01-31 15:15:36 +00002048// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002049static int find_max_y(const SkPoint pts[], int count) {
2050 SkASSERT(count > 0);
2051 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002052 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002053 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002054 SkScalar y = pts[i].fY;
2055 if (y > max) {
2056 max = y;
2057 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002058 }
2059 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002060 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002061}
2062
reed@google.comcabaf1d2012-01-11 21:03:05 +00002063static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2064 int i = index;
2065 for (;;) {
2066 i = (i + inc) % n;
2067 if (i == index) { // we wrapped around, so abort
2068 break;
2069 }
2070 if (pts[index] != pts[i]) { // found a different point, success!
2071 break;
2072 }
2073 }
2074 return i;
2075}
2076
reed@google.comc1ea60a2012-01-31 15:15:36 +00002077/**
2078 * Starting at index, and moving forward (incrementing), find the xmin and
2079 * xmax of the contiguous points that have the same Y.
2080 */
2081static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2082 int* maxIndexPtr) {
2083 const SkScalar y = pts[index].fY;
2084 SkScalar min = pts[index].fX;
2085 SkScalar max = min;
2086 int minIndex = index;
2087 int maxIndex = index;
2088 for (int i = index + 1; i < n; ++i) {
2089 if (pts[i].fY != y) {
2090 break;
2091 }
2092 SkScalar x = pts[i].fX;
2093 if (x < min) {
2094 min = x;
2095 minIndex = i;
2096 } else if (x > max) {
2097 max = x;
2098 maxIndex = i;
2099 }
2100 }
2101 *maxIndexPtr = maxIndex;
2102 return minIndex;
2103}
2104
reed@google.comac8543f2012-01-30 20:51:25 +00002105static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2106 if (dir) {
2107 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2108 }
2109 return true;
2110}
2111
reed@google.comc1ea60a2012-01-31 15:15:36 +00002112#if 0
2113#include "SkString.h"
2114#include "../utils/SkParsePath.h"
2115static void dumpPath(const SkPath& path) {
2116 SkString str;
2117 SkParsePath::ToSVGString(path, &str);
2118 SkDebugf("%s\n", str.c_str());
2119}
2120#endif
2121
reed@google.comac8543f2012-01-30 20:51:25 +00002122/*
2123 * We loop through all contours, and keep the computed cross-product of the
2124 * contour that contained the global y-max. If we just look at the first
2125 * contour, we may find one that is wound the opposite way (correctly) since
2126 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2127 * that is outer most (or at least has the global y-max) before we can consider
2128 * its cross product.
2129 */
reed@google.com69a99432012-01-10 18:00:10 +00002130bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002131// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002132 // don't want to pay the cost for computing this if it
2133 // is unknown, so we don't call isConvex()
2134 const Convexity conv = this->getConvexityOrUnknown();
2135
2136 ContourIter iter(fVerbs, fPts);
2137
reed@google.comac8543f2012-01-30 20:51:25 +00002138 // initialize with our logical y-min
2139 SkScalar ymax = this->getBounds().fTop;
2140 SkScalar ymaxCross = 0;
2141
reed@google.com69a99432012-01-10 18:00:10 +00002142 for (; !iter.done(); iter.next()) {
2143 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002144 if (n < 3) {
2145 continue;
2146 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002147
reed@google.comcabaf1d2012-01-11 21:03:05 +00002148 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002149 SkScalar cross = 0;
2150 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002151 // we loop, skipping over degenerate or flat segments that will
2152 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002153 for (int i = 0; i < n - 2; ++i) {
2154 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2155 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002156 // early-exit, as kConvex is assumed to have only 1
2157 // non-degenerate contour
2158 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002159 }
2160 }
2161 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002162 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002163 if (pts[index].fY < ymax) {
2164 continue;
2165 }
2166
reed@google.comc1ea60a2012-01-31 15:15:36 +00002167 // If there is more than 1 distinct point at the y-max, we take the
2168 // x-min and x-max of them and just subtract to compute the dir.
2169 if (pts[(index + 1) % n].fY == pts[index].fY) {
2170 int maxIndex;
2171 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002172 if (minIndex == maxIndex) {
2173 goto TRY_CROSSPROD;
2174 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002175 SkASSERT(pts[minIndex].fY == pts[index].fY);
2176 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2177 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2178 // we just subtract the indices, and let that auto-convert to
2179 // SkScalar, since we just want - or + to signal the direction.
2180 cross = minIndex - maxIndex;
2181 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002182 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002183 // Find a next and prev index to use for the cross-product test,
2184 // but we try to find pts that form non-zero vectors from pts[index]
2185 //
2186 // Its possible that we can't find two non-degenerate vectors, so
2187 // we have to guard our search (e.g. all the pts could be in the
2188 // same place).
2189
2190 // we pass n - 1 instead of -1 so we don't foul up % operator by
2191 // passing it a negative LH argument.
2192 int prev = find_diff_pt(pts, index, n, n - 1);
2193 if (prev == index) {
2194 // completely degenerate, skip to next contour
2195 continue;
2196 }
2197 int next = find_diff_pt(pts, index, n, 1);
2198 SkASSERT(next != index);
2199 cross = cross_prod(pts[prev], pts[index], pts[next]);
2200 // if we get a zero, but the pts aren't on top of each other, then
2201 // we can just look at the direction
2202 if (0 == cross) {
2203 // construct the subtract so we get the correct Direction below
2204 cross = pts[index].fX - pts[next].fX;
2205 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002206 }
reed@google.comac8543f2012-01-30 20:51:25 +00002207
2208 if (cross) {
2209 // record our best guess so far
2210 ymax = pts[index].fY;
2211 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002212 }
reed@google.com69a99432012-01-10 18:00:10 +00002213 }
2214 }
reed@google.com69a99432012-01-10 18:00:10 +00002215
reed@google.comac8543f2012-01-30 20:51:25 +00002216 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2217}