blob: 38619771a398997f810bfc84ba17af4a97869c23 [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"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
13
reed@google.com744faba2012-05-29 19:54:52 +000014// This value is just made-up for now. When count is 4, calling memset was much
15// slower than just writing the loop. This seems odd, and hopefully in the
16// future this we appear to have been a fluke...
17#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
18
reed@android.com8a1c16f2008-12-17 15:59:43 +000019////////////////////////////////////////////////////////////////////////////
20
reed@google.com3563c9e2011-11-14 19:34:57 +000021/**
22 * Path.bounds is defined to be the bounds of all the control points.
23 * If we called bounds.join(r) we would skip r if r was empty, which breaks
24 * our promise. Hence we have a custom joiner that doesn't look at emptiness
25 */
26static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
27 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
28 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
29 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
30 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
31}
32
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000033static bool is_degenerate(const SkPath& path) {
34 SkPath::Iter iter(path, false);
35 SkPoint pts[4];
36 return SkPath::kDone_Verb == iter.next(pts);
37}
38
bsalomon@google.com6aa29652012-04-18 13:29:52 +000039class SkAutoDisableOvalCheck {
40public:
41 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
42 fSaved = fPath->fIsOval;
43 }
44
45 ~SkAutoDisableOvalCheck() {
46 fPath->fIsOval = fSaved;
47 }
48
49private:
50 SkPath* fPath;
51 bool fSaved;
52};
53
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
59 cached bounds), and the 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
64 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() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000079 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000081 fPath->fBounds = fRect;
82 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000084 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000085 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 }
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 SkPath* fPath;
91 SkRect fRect;
92 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000093 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000094 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000095
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000097 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000098 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000099 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000102 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000103 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
106
reed@android.comd252db02009-04-01 18:31:44 +0000107static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
109 bounds->set(0, 0, 0, 0);
110 } else {
111 bounds->set(pts.begin(), pts.count());
112// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
113 }
114}
115
116////////////////////////////////////////////////////////////////////////////
117
118/*
119 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000120 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
122
123 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 1. If we encounter degenerate segments, remove them
125 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
126 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
127 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128*/
129
130////////////////////////////////////////////////////////////////////////////
131
reed@google.comd335d1d2012-01-12 18:17:11 +0000132// flag to require a moveTo if we begin with something else, like lineTo etc.
133#define INITIAL_LASTMOVETOINDEX_VALUE ~0
134
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000135SkPath::SkPath()
136 : fFillType(kWinding_FillType)
137 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000138 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000139 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000140 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000141 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000142#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000143 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000144 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000145#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
148SkPath::SkPath(const SkPath& src) {
149 SkDEBUGCODE(src.validate();)
150 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000151#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000153 fGenerationID = src.fGenerationID;
154 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000155#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156}
157
158SkPath::~SkPath() {
159 SkDEBUGCODE(this->validate();)
160}
161
162SkPath& SkPath::operator=(const SkPath& src) {
163 SkDEBUGCODE(src.validate();)
164
165 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000166 fBounds = src.fBounds;
167 fPts = src.fPts;
168 fVerbs = src.fVerbs;
169 fFillType = src.fFillType;
170 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000171 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000172 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000173 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000174 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000175 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 }
177 SkDEBUGCODE(this->validate();)
178 return *this;
179}
180
reed@android.com3abec1d2009-03-02 05:36:20 +0000181bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000182 // note: don't need to look at isConvex or bounds, since just comparing the
183 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000184
185 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
186 // since it is only a cache of info in the fVerbs, but its a fast way to
187 // notice a difference
188
reed@android.com3abec1d2009-03-02 05:36:20 +0000189 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000190 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
191 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000192}
193
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194void SkPath::swap(SkPath& other) {
195 SkASSERT(&other != NULL);
196
197 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000198 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199 fPts.swap(other.fPts);
200 fVerbs.swap(other.fVerbs);
201 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000202 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000203 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000204 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000205 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000206 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000207 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208 }
209}
210
djsollen@google.com56c69772011-11-08 19:00:26 +0000211#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000212uint32_t SkPath::getGenerationID() const {
213 return fGenerationID;
214}
djsollen@google.come63793a2012-03-21 15:39:03 +0000215
216const SkPath* SkPath::getSourcePath() const {
217 return fSourcePath;
218}
219
220void SkPath::setSourcePath(const SkPath* path) {
221 fSourcePath = path;
222}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000223#endif
224
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225void SkPath::reset() {
226 SkDEBUGCODE(this->validate();)
227
228 fPts.reset();
229 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000230 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000231 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000232 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000233 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000234 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000235 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236}
237
238void SkPath::rewind() {
239 SkDEBUGCODE(this->validate();)
240
241 fPts.rewind();
242 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000243 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000244 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000245 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000246 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000247 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000248 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249}
250
251bool SkPath::isEmpty() const {
252 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000253 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254}
255
reed@google.com7e6c4d12012-05-10 14:05:43 +0000256bool SkPath::isLine(SkPoint line[2]) const {
257 int verbCount = fVerbs.count();
258 int ptCount = fPts.count();
259
260 if (2 == verbCount && 2 == ptCount) {
261 const uint8_t* verbs = fVerbs.begin();
262 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
263 if (line) {
264 const SkPoint* pts = fPts.begin();
265 line[0] = pts[0];
266 line[1] = pts[1];
267 }
268 return true;
269 }
270 }
271 return false;
272}
273
caryclark@google.comf1316942011-07-26 19:54:45 +0000274/*
275 Determines if path is a rect by keeping track of changes in direction
276 and looking for a loop either clockwise or counterclockwise.
277
278 The direction is computed such that:
279 0: vertical up
280 1: horizontal right
281 2: vertical down
282 3: horizontal left
283
284A rectangle cycles up/right/down/left or up/left/down/right.
285
286The test fails if:
287 The path is closed, and followed by a line.
288 A second move creates a new endpoint.
289 A diagonal line is parsed.
290 There's more than four changes of direction.
291 There's a discontinuity on the line (e.g., a move in the middle)
292 The line reverses direction.
293 The rectangle doesn't complete a cycle.
294 The path contains a quadratic or cubic.
295 The path contains fewer than four points.
296 The final point isn't equal to the first point.
297
298It's OK if the path has:
299 Several colinear line segments composing a rectangle side.
300 Single points on the rectangle side.
301
302The direction takes advantage of the corners found since opposite sides
303must travel in opposite directions.
304
305FIXME: Allow colinear quads and cubics to be treated like lines.
306FIXME: If the API passes fill-only, return true if the filled stroke
307 is a rectangle, though the caller failed to close the path.
308 */
309bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000311
caryclark@google.comf1316942011-07-26 19:54:45 +0000312 int corners = 0;
313 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000314 first.set(0, 0);
315 last.set(0, 0);
316 int firstDirection = 0;
317 int lastDirection = 0;
318 int nextDirection = 0;
319 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000320 bool autoClose = false;
321 const uint8_t* verbs = fVerbs.begin();
322 const uint8_t* verbStop = fVerbs.end();
323 const SkPoint* pts = fPts.begin();
324 while (verbs != verbStop) {
325 switch (*verbs++) {
326 case kClose_Verb:
327 pts = fPts.begin();
328 autoClose = true;
329 case kLine_Verb: {
330 SkScalar left = last.fX;
331 SkScalar top = last.fY;
332 SkScalar right = pts->fX;
333 SkScalar bottom = pts->fY;
334 ++pts;
335 if (left != right && top != bottom) {
336 return false; // diagonal
337 }
338 if (left == right && top == bottom) {
339 break; // single point on side OK
340 }
341 nextDirection = (left != right) << 0 |
342 (left < right || top < bottom) << 1;
343 if (0 == corners) {
344 firstDirection = nextDirection;
345 first = last;
346 last = pts[-1];
347 corners = 1;
348 closedOrMoved = false;
349 break;
350 }
351 if (closedOrMoved) {
352 return false; // closed followed by a line
353 }
354 closedOrMoved = autoClose;
355 if (lastDirection != nextDirection) {
356 if (++corners > 4) {
357 return false; // too many direction changes
358 }
359 }
360 last = pts[-1];
361 if (lastDirection == nextDirection) {
362 break; // colinear segment
363 }
364 // Possible values for corners are 2, 3, and 4.
365 // When corners == 3, nextDirection opposes firstDirection.
366 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000367 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000368 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
369 if ((directionCycle ^ turn) != nextDirection) {
370 return false; // direction didn't follow cycle
371 }
372 break;
373 }
374 case kQuad_Verb:
375 case kCubic_Verb:
376 return false; // quadratic, cubic not allowed
377 case kMove_Verb:
378 last = *pts++;
379 closedOrMoved = true;
380 break;
381 }
382 lastDirection = nextDirection;
383 }
384 // Success if 4 corners and first point equals last
385 bool result = 4 == corners && first == last;
386 if (result && rect) {
387 *rect = getBounds();
388 }
389 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000390}
391
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000392int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393 SkDEBUGCODE(this->validate();)
394
395 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000396 SkASSERT(!max || dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 int count = fPts.count();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000398 fPts.copyRange(dst, 0, max);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 return count;
400}
401
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000402SkPoint SkPath::getPoint(int index) const {
403 if ((unsigned)index < (unsigned)fPts.count()) {
404 return fPts[index];
405 }
406 return SkPoint::Make(0, 0);
407}
408
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000409int SkPath::getVerbs(uint8_t dst[], int max) const {
410 SkDEBUGCODE(this->validate();)
411
412 SkASSERT(max >= 0);
413 SkASSERT(!max || dst);
414 fVerbs.copyRange(dst, 0, max);
415 return fVerbs.count();
416}
417
reed@google.com294dd7b2011-10-11 11:58:32 +0000418bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 SkDEBUGCODE(this->validate();)
420
reed@google.com294dd7b2011-10-11 11:58:32 +0000421 int count = fPts.count();
422 if (count > 0) {
423 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 *lastPt = fPts[count - 1];
425 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000426 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000428 if (lastPt) {
429 lastPt->set(0, 0);
430 }
431 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432}
433
434void SkPath::setLastPt(SkScalar x, SkScalar y) {
435 SkDEBUGCODE(this->validate();)
436
437 int count = fPts.count();
438 if (count == 0) {
439 this->moveTo(x, y);
440 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000441 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000443 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444 }
445}
446
reed@android.comd252db02009-04-01 18:31:44 +0000447void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000448 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000449 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450
reed@android.comd252db02009-04-01 18:31:44 +0000451 fBoundsIsDirty = false;
452 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453}
454
reed@google.com04863fa2011-05-15 04:08:24 +0000455void SkPath::setConvexity(Convexity c) {
456 if (fConvexity != c) {
457 fConvexity = c;
458 GEN_ID_INC;
459 }
460}
461
reed@android.com8a1c16f2008-12-17 15:59:43 +0000462//////////////////////////////////////////////////////////////////////////////
463// Construction methods
464
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000465#define DIRTY_AFTER_EDIT \
466 do { \
467 fBoundsIsDirty = true; \
468 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000469 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000470 } while (0)
471
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000472#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
473 do { \
474 fBoundsIsDirty = true; \
475 } while (0)
476
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477void SkPath::incReserve(U16CPU inc) {
478 SkDEBUGCODE(this->validate();)
479
480 fVerbs.setReserve(fVerbs.count() + inc);
481 fPts.setReserve(fPts.count() + inc);
482
483 SkDEBUGCODE(this->validate();)
484}
485
486void SkPath::moveTo(SkScalar x, SkScalar y) {
487 SkDEBUGCODE(this->validate();)
488
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489 SkPoint* pt;
490
reed@google.comd335d1d2012-01-12 18:17:11 +0000491 // remember our index
492 fLastMoveToIndex = fPts.count();
493
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000494 pt = fPts.append();
495 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000496 pt->set(x, y);
497
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000498 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000499 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000500}
501
502void SkPath::rMoveTo(SkScalar x, SkScalar y) {
503 SkPoint pt;
504 this->getLastPt(&pt);
505 this->moveTo(pt.fX + x, pt.fY + y);
506}
507
reed@google.comd335d1d2012-01-12 18:17:11 +0000508void SkPath::injectMoveToIfNeeded() {
509 if (fLastMoveToIndex < 0) {
510 SkScalar x, y;
511 if (fVerbs.count() == 0) {
512 x = y = 0;
513 } else {
514 const SkPoint& pt = fPts[~fLastMoveToIndex];
515 x = pt.fX;
516 y = pt.fY;
517 }
518 this->moveTo(x, y);
519 }
520}
521
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522void SkPath::lineTo(SkScalar x, SkScalar y) {
523 SkDEBUGCODE(this->validate();)
524
reed@google.comd335d1d2012-01-12 18:17:11 +0000525 this->injectMoveToIfNeeded();
526
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527 fPts.append()->set(x, y);
528 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000529 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000530
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000531 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000532 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533}
534
535void SkPath::rLineTo(SkScalar x, SkScalar y) {
536 SkPoint pt;
537 this->getLastPt(&pt);
538 this->lineTo(pt.fX + x, pt.fY + y);
539}
540
541void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
542 SkDEBUGCODE(this->validate();)
543
reed@google.comd335d1d2012-01-12 18:17:11 +0000544 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545
546 SkPoint* pts = fPts.append(2);
547 pts[0].set(x1, y1);
548 pts[1].set(x2, y2);
549 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000550 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000552 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000553 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554}
555
556void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
557 SkPoint pt;
558 this->getLastPt(&pt);
559 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
560}
561
562void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
563 SkScalar x3, SkScalar y3) {
564 SkDEBUGCODE(this->validate();)
565
reed@google.comd335d1d2012-01-12 18:17:11 +0000566 this->injectMoveToIfNeeded();
567
reed@android.com8a1c16f2008-12-17 15:59:43 +0000568 SkPoint* pts = fPts.append(3);
569 pts[0].set(x1, y1);
570 pts[1].set(x2, y2);
571 pts[2].set(x3, y3);
572 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000573 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000574
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000575 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000576 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577}
578
579void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
580 SkScalar x3, SkScalar y3) {
581 SkPoint pt;
582 this->getLastPt(&pt);
583 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
584 pt.fX + x3, pt.fY + y3);
585}
586
587void SkPath::close() {
588 SkDEBUGCODE(this->validate();)
589
590 int count = fVerbs.count();
591 if (count > 0) {
592 switch (fVerbs[count - 1]) {
593 case kLine_Verb:
594 case kQuad_Verb:
595 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000596 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000598 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 break;
600 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000601 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000602 break;
603 }
604 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000605
606 // signal that we need a moveTo to follow us (unless we're done)
607#if 0
608 if (fLastMoveToIndex >= 0) {
609 fLastMoveToIndex = ~fLastMoveToIndex;
610 }
611#else
612 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
613#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614}
615
616///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000617
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618void SkPath::addRect(const SkRect& rect, Direction dir) {
619 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
620}
621
622void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
623 SkScalar bottom, Direction dir) {
624 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
625
626 this->incReserve(5);
627
628 this->moveTo(left, top);
629 if (dir == kCCW_Direction) {
630 this->lineTo(left, bottom);
631 this->lineTo(right, bottom);
632 this->lineTo(right, top);
633 } else {
634 this->lineTo(right, top);
635 this->lineTo(right, bottom);
636 this->lineTo(left, bottom);
637 }
638 this->close();
639}
640
reed@google.com744faba2012-05-29 19:54:52 +0000641void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
642 SkDEBUGCODE(this->validate();)
643 if (count <= 0) {
644 return;
645 }
646
647 fLastMoveToIndex = fPts.count();
648 fPts.append(count, pts);
649
650 // +close makes room for the extra kClose_Verb
651 uint8_t* vb = fVerbs.append(count + close);
652 vb[0] = kMove_Verb;
653
654 if (count > 1) {
655 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
656 // be 0, the compiler will remove the test/branch entirely.
657 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
658 memset(&vb[1], kLine_Verb, count - 1);
659 } else {
660 for (int i = 1; i < count; ++i) {
661 vb[i] = kLine_Verb;
662 }
663 }
664 fSegmentMask |= kLine_SegmentMask;
665 }
666 if (close) {
667 vb[count] = kClose_Verb;
668 }
669
670 GEN_ID_INC;
671 DIRTY_AFTER_EDIT;
672}
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
675
676void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
677 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 SkScalar w = rect.width();
679 SkScalar halfW = SkScalarHalf(w);
680 SkScalar h = rect.height();
681 SkScalar halfH = SkScalarHalf(h);
682
683 if (halfW <= 0 || halfH <= 0) {
684 return;
685 }
686
reed@google.comabf15c12011-01-18 20:35:51 +0000687 bool skip_hori = rx >= halfW;
688 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689
690 if (skip_hori && skip_vert) {
691 this->addOval(rect, dir);
692 return;
693 }
reed@google.comabf15c12011-01-18 20:35:51 +0000694
695 SkAutoPathBoundsUpdate apbu(this, rect);
696
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 if (skip_hori) {
698 rx = halfW;
699 } else if (skip_vert) {
700 ry = halfH;
701 }
702
703 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
704 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
705
706 this->incReserve(17);
707 this->moveTo(rect.fRight - rx, rect.fTop);
708 if (dir == kCCW_Direction) {
709 if (!skip_hori) {
710 this->lineTo(rect.fLeft + rx, rect.fTop); // top
711 }
712 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
713 rect.fLeft, rect.fTop + ry - sy,
714 rect.fLeft, rect.fTop + ry); // top-left
715 if (!skip_vert) {
716 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
717 }
718 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
719 rect.fLeft + rx - sx, rect.fBottom,
720 rect.fLeft + rx, rect.fBottom); // bot-left
721 if (!skip_hori) {
722 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
723 }
724 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
725 rect.fRight, rect.fBottom - ry + sy,
726 rect.fRight, rect.fBottom - ry); // bot-right
727 if (!skip_vert) {
728 this->lineTo(rect.fRight, rect.fTop + ry);
729 }
730 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
731 rect.fRight - rx + sx, rect.fTop,
732 rect.fRight - rx, rect.fTop); // top-right
733 } else {
734 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
735 rect.fRight, rect.fTop + ry - sy,
736 rect.fRight, rect.fTop + ry); // top-right
737 if (!skip_vert) {
738 this->lineTo(rect.fRight, rect.fBottom - ry);
739 }
740 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
741 rect.fRight - rx + sx, rect.fBottom,
742 rect.fRight - rx, rect.fBottom); // bot-right
743 if (!skip_hori) {
744 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
745 }
746 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
747 rect.fLeft, rect.fBottom - ry + sy,
748 rect.fLeft, rect.fBottom - ry); // bot-left
749 if (!skip_vert) {
750 this->lineTo(rect.fLeft, rect.fTop + ry); // left
751 }
752 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
753 rect.fLeft + rx - sx, rect.fTop,
754 rect.fLeft + rx, rect.fTop); // top-left
755 if (!skip_hori) {
756 this->lineTo(rect.fRight - rx, rect.fTop); // top
757 }
758 }
759 this->close();
760}
761
762static void add_corner_arc(SkPath* path, const SkRect& rect,
763 SkScalar rx, SkScalar ry, int startAngle,
764 SkPath::Direction dir, bool forceMoveTo) {
765 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
766 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000767
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768 SkRect r;
769 r.set(-rx, -ry, rx, ry);
770
771 switch (startAngle) {
772 case 0:
773 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
774 break;
775 case 90:
776 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
777 break;
778 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
779 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000780 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 }
reed@google.comabf15c12011-01-18 20:35:51 +0000782
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 SkScalar start = SkIntToScalar(startAngle);
784 SkScalar sweep = SkIntToScalar(90);
785 if (SkPath::kCCW_Direction == dir) {
786 start += sweep;
787 sweep = -sweep;
788 }
reed@google.comabf15c12011-01-18 20:35:51 +0000789
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 path->arcTo(r, start, sweep, forceMoveTo);
791}
792
793void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
794 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000795 // abort before we invoke SkAutoPathBoundsUpdate()
796 if (rect.isEmpty()) {
797 return;
798 }
799
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 SkAutoPathBoundsUpdate apbu(this, rect);
801
802 if (kCW_Direction == dir) {
803 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
804 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
805 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
806 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
807 } else {
808 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
809 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
810 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
811 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
812 }
813 this->close();
814}
815
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000816bool SkPath::hasOnlyMoveTos() const {
817 const uint8_t* verbs = fVerbs.begin();
818 const uint8_t* verbStop = fVerbs.end();
819 while (verbs != verbStop) {
820 if (*verbs == kLine_Verb ||
821 *verbs == kQuad_Verb ||
822 *verbs == kCubic_Verb) {
823 return false;
824 }
825 ++verbs;
826 }
827 return true;
828}
829
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000831 /* If addOval() is called after previous moveTo(),
832 this path is still marked as an oval. This is used to
833 fit into WebKit's calling sequences.
834 We can't simply check isEmpty() in this case, as additional
835 moveTo() would mark the path non empty.
836 */
837 fIsOval = hasOnlyMoveTos();
838
839 SkAutoDisableOvalCheck adoc(this);
840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 SkAutoPathBoundsUpdate apbu(this, oval);
842
843 SkScalar cx = oval.centerX();
844 SkScalar cy = oval.centerY();
845 SkScalar rx = SkScalarHalf(oval.width());
846 SkScalar ry = SkScalarHalf(oval.height());
847#if 0 // these seem faster than using quads (1/2 the number of edges)
848 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
849 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
850
851 this->incReserve(13);
852 this->moveTo(cx + rx, cy);
853 if (dir == kCCW_Direction) {
854 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
855 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
856 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
857 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
858 } else {
859 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
860 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
861 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
862 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
863 }
864#else
865 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
866 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
867 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
868 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
869
870 /*
871 To handle imprecision in computing the center and radii, we revert to
872 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
873 to ensure that we don't exceed the oval's bounds *ever*, since we want
874 to use oval for our fast-bounds, rather than have to recompute it.
875 */
876 const SkScalar L = oval.fLeft; // cx - rx
877 const SkScalar T = oval.fTop; // cy - ry
878 const SkScalar R = oval.fRight; // cx + rx
879 const SkScalar B = oval.fBottom; // cy + ry
880
881 this->incReserve(17); // 8 quads + close
882 this->moveTo(R, cy);
883 if (dir == kCCW_Direction) {
884 this->quadTo( R, cy - sy, cx + mx, cy - my);
885 this->quadTo(cx + sx, T, cx , T);
886 this->quadTo(cx - sx, T, cx - mx, cy - my);
887 this->quadTo( L, cy - sy, L, cy );
888 this->quadTo( L, cy + sy, cx - mx, cy + my);
889 this->quadTo(cx - sx, B, cx , B);
890 this->quadTo(cx + sx, B, cx + mx, cy + my);
891 this->quadTo( R, cy + sy, R, cy );
892 } else {
893 this->quadTo( R, cy + sy, cx + mx, cy + my);
894 this->quadTo(cx + sx, B, cx , B);
895 this->quadTo(cx - sx, B, cx - mx, cy + my);
896 this->quadTo( L, cy + sy, L, cy );
897 this->quadTo( L, cy - sy, cx - mx, cy - my);
898 this->quadTo(cx - sx, T, cx , T);
899 this->quadTo(cx + sx, T, cx + mx, cy - my);
900 this->quadTo( R, cy - sy, R, cy );
901 }
902#endif
903 this->close();
904}
905
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000906bool SkPath::isOval(SkRect* rect) const {
907 if (fIsOval && rect) {
908 *rect = getBounds();
909 }
910
911 return fIsOval;
912}
913
reed@android.com8a1c16f2008-12-17 15:59:43 +0000914void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
915 if (r > 0) {
916 SkRect rect;
917 rect.set(x - r, y - r, x + r, y + r);
918 this->addOval(rect, dir);
919 }
920}
921
922#include "SkGeometry.h"
923
924static int build_arc_points(const SkRect& oval, SkScalar startAngle,
925 SkScalar sweepAngle,
926 SkPoint pts[kSkBuildQuadArcStorage]) {
927 SkVector start, stop;
928
929 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
930 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
931 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000932
933 /* If the sweep angle is nearly (but less than) 360, then due to precision
934 loss in radians-conversion and/or sin/cos, we may end up with coincident
935 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
936 of drawing a nearly complete circle (good).
937 e.g. canvas.drawArc(0, 359.99, ...)
938 -vs- canvas.drawArc(0, 359.9, ...)
939 We try to detect this edge case, and tweak the stop vector
940 */
941 if (start == stop) {
942 SkScalar sw = SkScalarAbs(sweepAngle);
943 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
944 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
945 // make a guess at a tiny angle (in radians) to tweak by
946 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
947 // not sure how much will be enough, so we use a loop
948 do {
949 stopRad -= deltaRad;
950 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
951 } while (start == stop);
952 }
953 }
954
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000956
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
958 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000959
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 return SkBuildQuadArc(start, stop,
961 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
962 &matrix, pts);
963}
964
965void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
966 bool forceMoveTo) {
967 if (oval.width() < 0 || oval.height() < 0) {
968 return;
969 }
970
971 SkPoint pts[kSkBuildQuadArcStorage];
972 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
973 SkASSERT((count & 1) == 1);
974
975 if (fVerbs.count() == 0) {
976 forceMoveTo = true;
977 }
978 this->incReserve(count);
979 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
980 for (int i = 1; i < count; i += 2) {
981 this->quadTo(pts[i], pts[i+1]);
982 }
983}
984
985void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
986 SkScalar sweepAngle) {
987 if (oval.isEmpty() || 0 == sweepAngle) {
988 return;
989 }
990
991 const SkScalar kFullCircleAngle = SkIntToScalar(360);
992
993 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
994 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
995 return;
996 }
997
998 SkPoint pts[kSkBuildQuadArcStorage];
999 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1000
1001 this->incReserve(count);
1002 this->moveTo(pts[0]);
1003 for (int i = 1; i < count; i += 2) {
1004 this->quadTo(pts[i], pts[i+1]);
1005 }
1006}
1007
1008/*
1009 Need to handle the case when the angle is sharp, and our computed end-points
1010 for the arc go behind pt1 and/or p2...
1011*/
1012void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1013 SkScalar radius) {
1014 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001015
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 // need to know our prev pt so we can construct tangent vectors
1017 {
1018 SkPoint start;
1019 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001020 // Handle degenerate cases by adding a line to the first point and
1021 // bailing out.
1022 if ((x1 == start.fX && y1 == start.fY) ||
1023 (x1 == x2 && y1 == y2) ||
1024 radius == 0) {
1025 this->lineTo(x1, y1);
1026 return;
1027 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028 before.setNormalize(x1 - start.fX, y1 - start.fY);
1029 after.setNormalize(x2 - x1, y2 - y1);
1030 }
reed@google.comabf15c12011-01-18 20:35:51 +00001031
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032 SkScalar cosh = SkPoint::DotProduct(before, after);
1033 SkScalar sinh = SkPoint::CrossProduct(before, after);
1034
1035 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001036 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 return;
1038 }
reed@google.comabf15c12011-01-18 20:35:51 +00001039
reed@android.com8a1c16f2008-12-17 15:59:43 +00001040 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1041 if (dist < 0) {
1042 dist = -dist;
1043 }
1044
1045 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1046 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1047 SkRotationDirection arcDir;
1048
1049 // now turn before/after into normals
1050 if (sinh > 0) {
1051 before.rotateCCW();
1052 after.rotateCCW();
1053 arcDir = kCW_SkRotationDirection;
1054 } else {
1055 before.rotateCW();
1056 after.rotateCW();
1057 arcDir = kCCW_SkRotationDirection;
1058 }
1059
1060 SkMatrix matrix;
1061 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001062
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 matrix.setScale(radius, radius);
1064 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1065 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001066
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001068
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 this->incReserve(count);
1070 // [xx,yy] == pts[0]
1071 this->lineTo(xx, yy);
1072 for (int i = 1; i < count; i += 2) {
1073 this->quadTo(pts[i], pts[i+1]);
1074 }
1075}
1076
1077///////////////////////////////////////////////////////////////////////////////
1078
1079void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1080 SkMatrix matrix;
1081
1082 matrix.setTranslate(dx, dy);
1083 this->addPath(path, matrix);
1084}
1085
1086void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1087 this->incReserve(path.fPts.count());
1088
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001089 fIsOval = false;
1090
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001091 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092 SkPoint pts[4];
1093 Verb verb;
1094
1095 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1096
1097 while ((verb = iter.next(pts)) != kDone_Verb) {
1098 switch (verb) {
1099 case kMove_Verb:
1100 proc(matrix, &pts[0], &pts[0], 1);
1101 this->moveTo(pts[0]);
1102 break;
1103 case kLine_Verb:
1104 proc(matrix, &pts[1], &pts[1], 1);
1105 this->lineTo(pts[1]);
1106 break;
1107 case kQuad_Verb:
1108 proc(matrix, &pts[1], &pts[1], 2);
1109 this->quadTo(pts[1], pts[2]);
1110 break;
1111 case kCubic_Verb:
1112 proc(matrix, &pts[1], &pts[1], 3);
1113 this->cubicTo(pts[1], pts[2], pts[3]);
1114 break;
1115 case kClose_Verb:
1116 this->close();
1117 break;
1118 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001119 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120 }
1121 }
1122}
1123
1124///////////////////////////////////////////////////////////////////////////////
1125
1126static const uint8_t gPtsInVerb[] = {
1127 1, // kMove
1128 1, // kLine
1129 2, // kQuad
1130 3, // kCubic
1131 0, // kClose
1132 0 // kDone
1133};
1134
1135// ignore the initial moveto, and stop when the 1st contour ends
1136void SkPath::pathTo(const SkPath& path) {
1137 int i, vcount = path.fVerbs.count();
1138 if (vcount == 0) {
1139 return;
1140 }
1141
1142 this->incReserve(vcount);
1143
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001144 fIsOval = false;
1145
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 const uint8_t* verbs = path.fVerbs.begin();
1147 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1148
1149 SkASSERT(verbs[0] == kMove_Verb);
1150 for (i = 1; i < vcount; i++) {
1151 switch (verbs[i]) {
1152 case kLine_Verb:
1153 this->lineTo(pts[0].fX, pts[0].fY);
1154 break;
1155 case kQuad_Verb:
1156 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1157 break;
1158 case kCubic_Verb:
1159 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1160 pts[2].fX, pts[2].fY);
1161 break;
1162 case kClose_Verb:
1163 return;
1164 }
1165 pts += gPtsInVerb[verbs[i]];
1166 }
1167}
1168
1169// ignore the last point of the 1st contour
1170void SkPath::reversePathTo(const SkPath& path) {
1171 int i, vcount = path.fVerbs.count();
1172 if (vcount == 0) {
1173 return;
1174 }
1175
1176 this->incReserve(vcount);
1177
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001178 fIsOval = false;
1179
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 const uint8_t* verbs = path.fVerbs.begin();
1181 const SkPoint* pts = path.fPts.begin();
1182
1183 SkASSERT(verbs[0] == kMove_Verb);
1184 for (i = 1; i < vcount; i++) {
1185 int n = gPtsInVerb[verbs[i]];
1186 if (n == 0) {
1187 break;
1188 }
1189 pts += n;
1190 }
1191
1192 while (--i > 0) {
1193 switch (verbs[i]) {
1194 case kLine_Verb:
1195 this->lineTo(pts[-1].fX, pts[-1].fY);
1196 break;
1197 case kQuad_Verb:
1198 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1199 break;
1200 case kCubic_Verb:
1201 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1202 pts[-3].fX, pts[-3].fY);
1203 break;
1204 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001205 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 break;
1207 }
1208 pts -= gPtsInVerb[verbs[i]];
1209 }
1210}
1211
reed@google.com63d73742012-01-10 15:33:12 +00001212void SkPath::reverseAddPath(const SkPath& src) {
1213 this->incReserve(src.fPts.count());
1214
reed@google.com63d73742012-01-10 15:33:12 +00001215 const SkPoint* pts = src.fPts.end();
1216 const uint8_t* startVerbs = src.fVerbs.begin();
1217 const uint8_t* verbs = src.fVerbs.end();
1218
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001219 fIsOval = false;
1220
reed@google.com63d73742012-01-10 15:33:12 +00001221 bool needMove = true;
1222 bool needClose = false;
1223 while (verbs > startVerbs) {
1224 uint8_t v = *--verbs;
1225 int n = gPtsInVerb[v];
1226
1227 if (needMove) {
1228 --pts;
1229 this->moveTo(pts->fX, pts->fY);
1230 needMove = false;
1231 }
1232 pts -= n;
1233 switch (v) {
1234 case kMove_Verb:
1235 if (needClose) {
1236 this->close();
1237 needClose = false;
1238 }
1239 needMove = true;
1240 pts += 1; // so we see the point in "if (needMove)" above
1241 break;
1242 case kLine_Verb:
1243 this->lineTo(pts[0]);
1244 break;
1245 case kQuad_Verb:
1246 this->quadTo(pts[1], pts[0]);
1247 break;
1248 case kCubic_Verb:
1249 this->cubicTo(pts[2], pts[1], pts[0]);
1250 break;
1251 case kClose_Verb:
1252 needClose = true;
1253 break;
1254 default:
1255 SkASSERT(!"unexpected verb");
1256 }
1257 }
1258}
1259
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260///////////////////////////////////////////////////////////////////////////////
1261
1262void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1263 SkMatrix matrix;
1264
1265 matrix.setTranslate(dx, dy);
1266 this->transform(matrix, dst);
1267}
1268
1269#include "SkGeometry.h"
1270
1271static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1272 int level = 2) {
1273 if (--level >= 0) {
1274 SkPoint tmp[5];
1275
1276 SkChopQuadAtHalf(pts, tmp);
1277 subdivide_quad_to(path, &tmp[0], level);
1278 subdivide_quad_to(path, &tmp[2], level);
1279 } else {
1280 path->quadTo(pts[1], pts[2]);
1281 }
1282}
1283
1284static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1285 int level = 2) {
1286 if (--level >= 0) {
1287 SkPoint tmp[7];
1288
1289 SkChopCubicAtHalf(pts, tmp);
1290 subdivide_cubic_to(path, &tmp[0], level);
1291 subdivide_cubic_to(path, &tmp[3], level);
1292 } else {
1293 path->cubicTo(pts[1], pts[2], pts[3]);
1294 }
1295}
1296
1297void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1298 SkDEBUGCODE(this->validate();)
1299 if (dst == NULL) {
1300 dst = (SkPath*)this;
1301 }
1302
tomhudson@google.com8d430182011-06-06 19:11:19 +00001303 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 SkPath tmp;
1305 tmp.fFillType = fFillType;
1306
1307 SkPath::Iter iter(*this, false);
1308 SkPoint pts[4];
1309 SkPath::Verb verb;
1310
reed@google.com4a3b7142012-05-16 17:16:46 +00001311 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 switch (verb) {
1313 case kMove_Verb:
1314 tmp.moveTo(pts[0]);
1315 break;
1316 case kLine_Verb:
1317 tmp.lineTo(pts[1]);
1318 break;
1319 case kQuad_Verb:
1320 subdivide_quad_to(&tmp, pts);
1321 break;
1322 case kCubic_Verb:
1323 subdivide_cubic_to(&tmp, pts);
1324 break;
1325 case kClose_Verb:
1326 tmp.close();
1327 break;
1328 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001329 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 break;
1331 }
1332 }
1333
1334 dst->swap(tmp);
1335 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1336 } else {
1337 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001338 // fBoundsIsDirty before we set it
1339 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001341 matrix.mapRect(&dst->fBounds, fBounds);
1342 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001344 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001345 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 }
1347
1348 if (this != dst) {
1349 dst->fVerbs = fVerbs;
1350 dst->fPts.setCount(fPts.count());
1351 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001352 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001353 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001354 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001356
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001358
1359 if (fIsOval) {
1360 // It's an oval only if it stays a rect.
1361 dst->fIsOval = matrix.rectStaysRect();
1362 }
1363
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 SkDEBUGCODE(dst->validate();)
1365 }
1366}
1367
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368///////////////////////////////////////////////////////////////////////////////
1369///////////////////////////////////////////////////////////////////////////////
1370
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001371enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001372 kEmptyContour_SegmentState, // The current contour is empty. We may be
1373 // starting processing or we may have just
1374 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001375 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1376 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1377 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378};
1379
1380SkPath::Iter::Iter() {
1381#ifdef SK_DEBUG
1382 fPts = NULL;
1383 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001384 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001385 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386#endif
1387 // need to init enough to make next() harmlessly return kDone_Verb
1388 fVerbs = NULL;
1389 fVerbStop = NULL;
1390 fNeedClose = false;
1391}
1392
1393SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1394 this->setPath(path, forceClose);
1395}
1396
1397void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1398 fPts = path.fPts.begin();
1399 fVerbs = path.fVerbs.begin();
1400 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001401 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001402 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403 fForceClose = SkToU8(forceClose);
1404 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001405 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406}
1407
1408bool SkPath::Iter::isClosedContour() const {
1409 if (fVerbs == NULL || fVerbs == fVerbStop) {
1410 return false;
1411 }
1412 if (fForceClose) {
1413 return true;
1414 }
1415
1416 const uint8_t* verbs = fVerbs;
1417 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001418
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 if (kMove_Verb == *verbs) {
1420 verbs += 1; // skip the initial moveto
1421 }
1422
1423 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001424 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 if (kMove_Verb == v) {
1426 break;
1427 }
1428 if (kClose_Verb == v) {
1429 return true;
1430 }
1431 }
1432 return false;
1433}
1434
1435SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001436 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001438 // A special case: if both points are NaN, SkPoint::operation== returns
1439 // false, but the iterator expects that they are treated as the same.
1440 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001441 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1442 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001443 return kClose_Verb;
1444 }
1445
reed@google.com9e25dbf2012-05-15 17:05:38 +00001446 pts[0] = fLastPt;
1447 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 fLastPt = fMoveTo;
1449 fCloseLine = true;
1450 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001451 } else {
1452 pts[0] = fMoveTo;
1453 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455}
1456
reed@google.com9e25dbf2012-05-15 17:05:38 +00001457const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001458 if (fSegmentState == kAfterMove_SegmentState) {
1459 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001460 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001461 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001463 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1464 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001465 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467}
1468
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001469void SkPath::Iter::consumeDegenerateSegments() {
1470 // We need to step over anything that will not move the current draw point
1471 // forward before the next move is seen
1472 const uint8_t* lastMoveVerb = 0;
1473 const SkPoint* lastMovePt = 0;
1474 SkPoint lastPt = fLastPt;
1475 while (fVerbs != fVerbStop) {
1476 unsigned verb = *fVerbs;
1477 switch (verb) {
1478 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001479 // Keep a record of this most recent move
1480 lastMoveVerb = fVerbs;
1481 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001482 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001483 fVerbs++;
1484 fPts++;
1485 break;
1486
1487 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001488 // A close when we are in a segment is always valid except when it
1489 // follows a move which follows a segment.
1490 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001491 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/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001688 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689*/
1690
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001691uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 SkDEBUGCODE(this->validate();)
1693
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001694 if (NULL == storage) {
1695 const int byteCount = 3 * sizeof(int32_t)
1696 + sizeof(SkPoint) * fPts.count()
1697 + sizeof(uint8_t) * fVerbs.count();
1698 return SkAlign4(byteCount);
1699 }
1700
1701 SkWBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 buffer.write32(fPts.count());
1703 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001704 buffer.write32((fFillType << 8) | fSegmentMask);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001705 buffer.write(fPts.begin(), sizeof(SkPoint) * fPts.count());
1706 buffer.write(fVerbs.begin(), fVerbs.count());
1707 buffer.padToAlign4();
1708 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709}
1710
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001711uint32_t SkPath::readFromMemory(const void* storage) {
1712 SkRBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713 fPts.setCount(buffer.readS32());
1714 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001715 uint32_t packed = buffer.readS32();
1716 fFillType = packed >> 8;
1717 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1719 buffer.read(fVerbs.begin(), fVerbs.count());
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001720 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001721
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001722 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001723 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724
1725 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001726 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727}
1728
1729///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731void SkPath::dump(bool forceClose, const char title[]) const {
1732 Iter iter(*this, forceClose);
1733 SkPoint pts[4];
1734 Verb verb;
1735
1736 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1737 title ? title : "");
1738
reed@google.com4a3b7142012-05-16 17:16:46 +00001739 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 switch (verb) {
1741 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 SkDebugf(" path: moveTo [%g %g]\n",
1743 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 break;
1745 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 SkDebugf(" path: lineTo [%g %g]\n",
1747 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 break;
1749 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1751 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1752 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 break;
1754 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1756 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1757 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1758 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 break;
1760 case kClose_Verb:
1761 SkDebugf(" path: close\n");
1762 break;
1763 default:
1764 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1765 verb = kDone_Verb; // stop the loop
1766 break;
1767 }
1768 }
1769 SkDebugf("path: done %s\n", title ? title : "");
1770}
1771
reed@android.come522ca52009-11-23 20:10:41 +00001772void SkPath::dump() const {
1773 this->dump(false);
1774}
1775
1776#ifdef SK_DEBUG
1777void SkPath::validate() const {
1778 SkASSERT(this != NULL);
1779 SkASSERT((fFillType & ~3) == 0);
1780 fPts.validate();
1781 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001782
reed@android.come522ca52009-11-23 20:10:41 +00001783 if (!fBoundsIsDirty) {
1784 SkRect bounds;
1785 compute_pt_bounds(&bounds, fPts);
1786 if (fPts.count() <= 1) {
1787 // if we're empty, fBounds may be empty but translated, so we can't
1788 // necessarily compare to bounds directly
1789 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1790 // be [2, 2, 2, 2]
1791 SkASSERT(bounds.isEmpty());
1792 SkASSERT(fBounds.isEmpty());
1793 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001794 if (bounds.isEmpty()) {
1795 SkASSERT(fBounds.isEmpty());
1796 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001797 if (!fBounds.isEmpty()) {
1798 SkASSERT(fBounds.contains(bounds));
1799 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001800 }
reed@android.come522ca52009-11-23 20:10:41 +00001801 }
1802 }
reed@google.com10296cc2011-09-21 12:29:05 +00001803
1804 uint32_t mask = 0;
1805 for (int i = 0; i < fVerbs.count(); i++) {
1806 switch (fVerbs[i]) {
1807 case kLine_Verb:
1808 mask |= kLine_SegmentMask;
1809 break;
1810 case kQuad_Verb:
1811 mask |= kQuad_SegmentMask;
1812 break;
1813 case kCubic_Verb:
1814 mask |= kCubic_SegmentMask;
1815 }
1816 }
1817 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001818}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819#endif
reed@android.come522ca52009-11-23 20:10:41 +00001820
reed@google.com04863fa2011-05-15 04:08:24 +00001821///////////////////////////////////////////////////////////////////////////////
1822
reed@google.com0b7b9822011-05-16 12:29:27 +00001823static int sign(SkScalar x) { return x < 0; }
1824#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001825
reed@google.com04863fa2011-05-15 04:08:24 +00001826static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001827 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001828}
1829
1830// only valid for a single contour
1831struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001832 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001833 fSign = 0;
1834 // warnings
1835 fCurrPt.set(0, 0);
1836 fVec0.set(0, 0);
1837 fVec1.set(0, 0);
1838 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001839
1840 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001841 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001842 }
1843
1844 SkPath::Convexity getConvexity() const { return fConvexity; }
1845
1846 void addPt(const SkPoint& pt) {
1847 if (SkPath::kConcave_Convexity == fConvexity) {
1848 return;
1849 }
1850
1851 if (0 == fPtCount) {
1852 fCurrPt = pt;
1853 ++fPtCount;
1854 } else {
1855 SkVector vec = pt - fCurrPt;
1856 if (vec.fX || vec.fY) {
1857 fCurrPt = pt;
1858 if (++fPtCount == 2) {
1859 fFirstVec = fVec1 = vec;
1860 } else {
1861 SkASSERT(fPtCount > 2);
1862 this->addVec(vec);
1863 }
reed@google.com85b6e392011-05-15 20:25:17 +00001864
1865 int sx = sign(vec.fX);
1866 int sy = sign(vec.fY);
1867 fDx += (sx != fSx);
1868 fDy += (sy != fSy);
1869 fSx = sx;
1870 fSy = sy;
1871
1872 if (fDx > 3 || fDy > 3) {
1873 fConvexity = SkPath::kConcave_Convexity;
1874 }
reed@google.com04863fa2011-05-15 04:08:24 +00001875 }
1876 }
1877 }
1878
1879 void close() {
1880 if (fPtCount > 2) {
1881 this->addVec(fFirstVec);
1882 }
1883 }
1884
1885private:
1886 void addVec(const SkVector& vec) {
1887 SkASSERT(vec.fX || vec.fY);
1888 fVec0 = fVec1;
1889 fVec1 = vec;
1890 int sign = CrossProductSign(fVec0, fVec1);
1891 if (0 == fSign) {
1892 fSign = sign;
1893 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001894 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001895 fConvexity = SkPath::kConcave_Convexity;
1896 }
1897 }
1898 }
1899
1900 SkPoint fCurrPt;
1901 SkVector fVec0, fVec1, fFirstVec;
1902 int fPtCount; // non-degenerate points
1903 int fSign;
1904 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001905 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001906};
1907
1908SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1909 SkPoint pts[4];
1910 SkPath::Verb verb;
1911 SkPath::Iter iter(path, true);
1912
1913 int contourCount = 0;
1914 int count;
1915 Convexicator state;
1916
1917 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1918 switch (verb) {
1919 case kMove_Verb:
1920 if (++contourCount > 1) {
1921 return kConcave_Convexity;
1922 }
1923 pts[1] = pts[0];
1924 count = 1;
1925 break;
1926 case kLine_Verb: count = 1; break;
1927 case kQuad_Verb: count = 2; break;
1928 case kCubic_Verb: count = 3; break;
1929 case kClose_Verb:
1930 state.close();
1931 count = 0;
1932 break;
1933 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001934 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001935 return kConcave_Convexity;
1936 }
1937
1938 for (int i = 1; i <= count; i++) {
1939 state.addPt(pts[i]);
1940 }
1941 // early exit
1942 if (kConcave_Convexity == state.getConvexity()) {
1943 return kConcave_Convexity;
1944 }
1945 }
1946 return state.getConvexity();
1947}
reed@google.com69a99432012-01-10 18:00:10 +00001948
1949///////////////////////////////////////////////////////////////////////////////
1950
1951class ContourIter {
1952public:
1953 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1954
1955 bool done() const { return fDone; }
1956 // if !done() then these may be called
1957 int count() const { return fCurrPtCount; }
1958 const SkPoint* pts() const { return fCurrPt; }
1959 void next();
1960
1961private:
1962 int fCurrPtCount;
1963 const SkPoint* fCurrPt;
1964 const uint8_t* fCurrVerb;
1965 const uint8_t* fStopVerbs;
1966 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001967 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001968};
1969
1970ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1971 const SkTDArray<SkPoint>& pts) {
1972 fStopVerbs = verbs.begin() + verbs.count();
1973
1974 fDone = false;
1975 fCurrPt = pts.begin();
1976 fCurrVerb = verbs.begin();
1977 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001978 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001979 this->next();
1980}
1981
1982void ContourIter::next() {
1983 if (fCurrVerb >= fStopVerbs) {
1984 fDone = true;
1985 }
1986 if (fDone) {
1987 return;
1988 }
1989
1990 // skip pts of prev contour
1991 fCurrPt += fCurrPtCount;
1992
1993 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1994 int ptCount = 1; // moveTo
1995 const uint8_t* verbs = fCurrVerb;
1996
1997 for (++verbs; verbs < fStopVerbs; ++verbs) {
1998 switch (*verbs) {
1999 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002000 goto CONTOUR_END;
2001 case SkPath::kLine_Verb:
2002 ptCount += 1;
2003 break;
2004 case SkPath::kQuad_Verb:
2005 ptCount += 2;
2006 break;
2007 case SkPath::kCubic_Verb:
2008 ptCount += 3;
2009 break;
2010 default: // kClose_Verb, just keep going
2011 break;
2012 }
2013 }
2014CONTOUR_END:
2015 fCurrPtCount = ptCount;
2016 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002017 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002018}
2019
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002020// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002021static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002022 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2023 // We may get 0 when the above subtracts underflow. We expect this to be
2024 // very rare and lazily promote to double.
2025 if (0 == cross) {
2026 double p0x = SkScalarToDouble(p0.fX);
2027 double p0y = SkScalarToDouble(p0.fY);
2028
2029 double p1x = SkScalarToDouble(p1.fX);
2030 double p1y = SkScalarToDouble(p1.fY);
2031
2032 double p2x = SkScalarToDouble(p2.fX);
2033 double p2y = SkScalarToDouble(p2.fY);
2034
2035 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2036 (p1y - p0y) * (p2x - p0x));
2037
2038 }
2039 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002040}
2041
reed@google.comc1ea60a2012-01-31 15:15:36 +00002042// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002043static int find_max_y(const SkPoint pts[], int count) {
2044 SkASSERT(count > 0);
2045 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002046 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002047 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002048 SkScalar y = pts[i].fY;
2049 if (y > max) {
2050 max = y;
2051 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002052 }
2053 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002054 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002055}
2056
reed@google.comcabaf1d2012-01-11 21:03:05 +00002057static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2058 int i = index;
2059 for (;;) {
2060 i = (i + inc) % n;
2061 if (i == index) { // we wrapped around, so abort
2062 break;
2063 }
2064 if (pts[index] != pts[i]) { // found a different point, success!
2065 break;
2066 }
2067 }
2068 return i;
2069}
2070
reed@google.comc1ea60a2012-01-31 15:15:36 +00002071/**
2072 * Starting at index, and moving forward (incrementing), find the xmin and
2073 * xmax of the contiguous points that have the same Y.
2074 */
2075static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2076 int* maxIndexPtr) {
2077 const SkScalar y = pts[index].fY;
2078 SkScalar min = pts[index].fX;
2079 SkScalar max = min;
2080 int minIndex = index;
2081 int maxIndex = index;
2082 for (int i = index + 1; i < n; ++i) {
2083 if (pts[i].fY != y) {
2084 break;
2085 }
2086 SkScalar x = pts[i].fX;
2087 if (x < min) {
2088 min = x;
2089 minIndex = i;
2090 } else if (x > max) {
2091 max = x;
2092 maxIndex = i;
2093 }
2094 }
2095 *maxIndexPtr = maxIndex;
2096 return minIndex;
2097}
2098
reed@google.comac8543f2012-01-30 20:51:25 +00002099static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2100 if (dir) {
2101 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2102 }
2103 return true;
2104}
2105
reed@google.comc1ea60a2012-01-31 15:15:36 +00002106#if 0
2107#include "SkString.h"
2108#include "../utils/SkParsePath.h"
2109static void dumpPath(const SkPath& path) {
2110 SkString str;
2111 SkParsePath::ToSVGString(path, &str);
2112 SkDebugf("%s\n", str.c_str());
2113}
2114#endif
2115
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002116namespace {
2117// for use with convex_dir_test
2118double mul(double a, double b) { return a * b; }
2119SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2120double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2121SkScalar toScalar(SkScalar a) { return a; }
2122
2123// determines the winding direction of a convex polygon with the precision
2124// of T. CAST_SCALAR casts an SkScalar to T.
2125template <typename T, T (CAST_SCALAR)(SkScalar)>
2126bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2127 // we find the first three points that form a non-degenerate
2128 // triangle. If there are no such points then the path is
2129 // degenerate. The first is always point 0. Now we find the second
2130 // point.
2131 int i = 0;
2132 enum { kX = 0, kY = 1 };
2133 T v0[2];
2134 while (1) {
2135 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2136 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2137 if (v0[kX] || v0[kY]) {
2138 break;
2139 }
2140 if (++i == n - 1) {
2141 return false;
2142 }
2143 }
2144 // now find a third point that is not colinear with the first two
2145 // points and check the orientation of the triangle (which will be
2146 // the same as the orientation of the path).
2147 for (++i; i < n; ++i) {
2148 T v1[2];
2149 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2150 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2151 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2152 if (0 != cross) {
2153 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2154 return true;
2155 }
2156 }
2157 return false;
2158}
2159}
2160
reed@google.comac8543f2012-01-30 20:51:25 +00002161/*
2162 * We loop through all contours, and keep the computed cross-product of the
2163 * contour that contained the global y-max. If we just look at the first
2164 * contour, we may find one that is wound the opposite way (correctly) since
2165 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2166 * that is outer most (or at least has the global y-max) before we can consider
2167 * its cross product.
2168 */
reed@google.com69a99432012-01-10 18:00:10 +00002169bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002170// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002171 // don't want to pay the cost for computing this if it
2172 // is unknown, so we don't call isConvex()
2173 const Convexity conv = this->getConvexityOrUnknown();
2174
2175 ContourIter iter(fVerbs, fPts);
2176
reed@google.comac8543f2012-01-30 20:51:25 +00002177 // initialize with our logical y-min
2178 SkScalar ymax = this->getBounds().fTop;
2179 SkScalar ymaxCross = 0;
2180
reed@google.com69a99432012-01-10 18:00:10 +00002181 for (; !iter.done(); iter.next()) {
2182 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002183 if (n < 3) {
2184 continue;
2185 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002186
reed@google.comcabaf1d2012-01-11 21:03:05 +00002187 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002188 SkScalar cross = 0;
2189 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002190 // We try first at scalar precision, and then again at double
2191 // precision. This is because the vectors computed between distant
2192 // points may lose too much precision.
2193 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2194 return true;
2195 }
2196 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2197 return true;
2198 } else {
2199 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002200 }
2201 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002202 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002203 if (pts[index].fY < ymax) {
2204 continue;
2205 }
2206
reed@google.comc1ea60a2012-01-31 15:15:36 +00002207 // If there is more than 1 distinct point at the y-max, we take the
2208 // x-min and x-max of them and just subtract to compute the dir.
2209 if (pts[(index + 1) % n].fY == pts[index].fY) {
2210 int maxIndex;
2211 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002212 if (minIndex == maxIndex) {
2213 goto TRY_CROSSPROD;
2214 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002215 SkASSERT(pts[minIndex].fY == pts[index].fY);
2216 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2217 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2218 // we just subtract the indices, and let that auto-convert to
2219 // SkScalar, since we just want - or + to signal the direction.
2220 cross = minIndex - maxIndex;
2221 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002222 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002223 // Find a next and prev index to use for the cross-product test,
2224 // but we try to find pts that form non-zero vectors from pts[index]
2225 //
2226 // Its possible that we can't find two non-degenerate vectors, so
2227 // we have to guard our search (e.g. all the pts could be in the
2228 // same place).
2229
2230 // we pass n - 1 instead of -1 so we don't foul up % operator by
2231 // passing it a negative LH argument.
2232 int prev = find_diff_pt(pts, index, n, n - 1);
2233 if (prev == index) {
2234 // completely degenerate, skip to next contour
2235 continue;
2236 }
2237 int next = find_diff_pt(pts, index, n, 1);
2238 SkASSERT(next != index);
2239 cross = cross_prod(pts[prev], pts[index], pts[next]);
2240 // if we get a zero, but the pts aren't on top of each other, then
2241 // we can just look at the direction
2242 if (0 == cross) {
2243 // construct the subtract so we get the correct Direction below
2244 cross = pts[index].fX - pts[next].fX;
2245 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002246 }
reed@google.comac8543f2012-01-30 20:51:25 +00002247
2248 if (cross) {
2249 // record our best guess so far
2250 ymax = pts[index].fY;
2251 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002252 }
reed@google.com69a99432012-01-10 18:00:10 +00002253 }
2254 }
reed@google.com69a99432012-01-10 18:00:10 +00002255
reed@google.comac8543f2012-01-30 20:51:25 +00002256 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2257}