blob: 034a515b50c8f676fb211bbec9298791c244b879 [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:
1488 // A close when we are in a segment is always valid
1489 if (fSegmentState == kAfterPrimitive_SegmentState) {
1490 return;
1491 }
1492 // A close at any other time must be ignored
1493 fVerbs++;
1494 break;
1495
1496 case kLine_Verb:
1497 if (!IsLineDegenerate(lastPt, fPts[0])) {
1498 if (lastMoveVerb) {
1499 fVerbs = lastMoveVerb;
1500 fPts = lastMovePt;
1501 return;
1502 }
1503 return;
1504 }
1505 // Ignore this line and continue
1506 fVerbs++;
1507 fPts++;
1508 break;
1509
1510 case kQuad_Verb:
1511 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1512 if (lastMoveVerb) {
1513 fVerbs = lastMoveVerb;
1514 fPts = lastMovePt;
1515 return;
1516 }
1517 return;
1518 }
1519 // Ignore this line and continue
1520 fVerbs++;
1521 fPts += 2;
1522 break;
1523
1524 case kCubic_Verb:
1525 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1526 if (lastMoveVerb) {
1527 fVerbs = lastMoveVerb;
1528 fPts = lastMovePt;
1529 return;
1530 }
1531 return;
1532 }
1533 // Ignore this line and continue
1534 fVerbs++;
1535 fPts += 3;
1536 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001537
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001538 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001539 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001540 }
1541 }
1542}
1543
reed@google.com4a3b7142012-05-16 17:16:46 +00001544SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001545 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001546
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001548 // Close the curve if requested and if there is some curve to close
1549 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001550 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 return kLine_Verb;
1552 }
1553 fNeedClose = false;
1554 return kClose_Verb;
1555 }
1556 return kDone_Verb;
1557 }
1558
1559 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001560 const SkPoint* SK_RESTRICT srcPts = fPts;
1561 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562
1563 switch (verb) {
1564 case kMove_Verb:
1565 if (fNeedClose) {
1566 fVerbs -= 1;
1567 verb = this->autoClose(pts);
1568 if (verb == kClose_Verb) {
1569 fNeedClose = false;
1570 }
1571 return (Verb)verb;
1572 }
1573 if (fVerbs == fVerbStop) { // might be a trailing moveto
1574 return kDone_Verb;
1575 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001576 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001577 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001579 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001580 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 fNeedClose = fForceClose;
1582 break;
1583 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001584 pts[0] = this->cons_moveTo();
1585 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 fLastPt = srcPts[0];
1587 fCloseLine = false;
1588 srcPts += 1;
1589 break;
1590 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001591 pts[0] = this->cons_moveTo();
1592 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593 fLastPt = srcPts[1];
1594 srcPts += 2;
1595 break;
1596 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001597 pts[0] = this->cons_moveTo();
1598 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 fLastPt = srcPts[2];
1600 srcPts += 3;
1601 break;
1602 case kClose_Verb:
1603 verb = this->autoClose(pts);
1604 if (verb == kLine_Verb) {
1605 fVerbs -= 1;
1606 } else {
1607 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001608 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 break;
1612 }
1613 fPts = srcPts;
1614 return (Verb)verb;
1615}
1616
1617///////////////////////////////////////////////////////////////////////////////
1618
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001619SkPath::RawIter::RawIter() {
1620#ifdef SK_DEBUG
1621 fPts = NULL;
1622 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1623#endif
1624 // need to init enough to make next() harmlessly return kDone_Verb
1625 fVerbs = NULL;
1626 fVerbStop = NULL;
1627}
1628
1629SkPath::RawIter::RawIter(const SkPath& path) {
1630 this->setPath(path);
1631}
1632
1633void SkPath::RawIter::setPath(const SkPath& path) {
1634 fPts = path.fPts.begin();
1635 fVerbs = path.fVerbs.begin();
1636 fVerbStop = path.fVerbs.end();
1637 fMoveTo.fX = fMoveTo.fY = 0;
1638 fLastPt.fX = fLastPt.fY = 0;
1639}
1640
1641SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001642 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001643 if (fVerbs == fVerbStop) {
1644 return kDone_Verb;
1645 }
1646
1647 unsigned verb = *fVerbs++;
1648 const SkPoint* srcPts = fPts;
1649
1650 switch (verb) {
1651 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001652 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001653 fMoveTo = srcPts[0];
1654 fLastPt = fMoveTo;
1655 srcPts += 1;
1656 break;
1657 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001658 pts[0] = fLastPt;
1659 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001660 fLastPt = srcPts[0];
1661 srcPts += 1;
1662 break;
1663 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001664 pts[0] = fLastPt;
1665 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001666 fLastPt = srcPts[1];
1667 srcPts += 2;
1668 break;
1669 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001670 pts[0] = fLastPt;
1671 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001672 fLastPt = srcPts[2];
1673 srcPts += 3;
1674 break;
1675 case kClose_Verb:
1676 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001677 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001678 break;
1679 }
1680 fPts = srcPts;
1681 return (Verb)verb;
1682}
1683
1684///////////////////////////////////////////////////////////////////////////////
1685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001687 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688*/
1689
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001690uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 SkDEBUGCODE(this->validate();)
1692
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001693 if (NULL == storage) {
1694 const int byteCount = 3 * sizeof(int32_t)
1695 + sizeof(SkPoint) * fPts.count()
1696 + sizeof(uint8_t) * fVerbs.count();
1697 return SkAlign4(byteCount);
1698 }
1699
1700 SkWBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 buffer.write32(fPts.count());
1702 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001703 buffer.write32((fFillType << 8) | fSegmentMask);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001704 buffer.write(fPts.begin(), sizeof(SkPoint) * fPts.count());
1705 buffer.write(fVerbs.begin(), fVerbs.count());
1706 buffer.padToAlign4();
1707 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708}
1709
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001710uint32_t SkPath::readFromMemory(const void* storage) {
1711 SkRBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 fPts.setCount(buffer.readS32());
1713 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001714 uint32_t packed = buffer.readS32();
1715 fFillType = packed >> 8;
1716 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1718 buffer.read(fVerbs.begin(), fVerbs.count());
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001719 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001720
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001721 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001722 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723
1724 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001725 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726}
1727
1728///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730void SkPath::dump(bool forceClose, const char title[]) const {
1731 Iter iter(*this, forceClose);
1732 SkPoint pts[4];
1733 Verb verb;
1734
1735 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1736 title ? title : "");
1737
reed@google.com4a3b7142012-05-16 17:16:46 +00001738 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 switch (verb) {
1740 case kMove_Verb:
1741#ifdef SK_CAN_USE_FLOAT
1742 SkDebugf(" path: moveTo [%g %g]\n",
1743 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1744#else
1745 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1746#endif
1747 break;
1748 case kLine_Verb:
1749#ifdef SK_CAN_USE_FLOAT
1750 SkDebugf(" path: lineTo [%g %g]\n",
1751 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1752#else
1753 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1754#endif
1755 break;
1756 case kQuad_Verb:
1757#ifdef SK_CAN_USE_FLOAT
1758 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1759 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1760 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1761#else
1762 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1763 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1764#endif
1765 break;
1766 case kCubic_Verb:
1767#ifdef SK_CAN_USE_FLOAT
1768 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1769 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1770 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1771 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1772#else
1773 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1774 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1775 pts[3].fX, pts[3].fY);
1776#endif
1777 break;
1778 case kClose_Verb:
1779 SkDebugf(" path: close\n");
1780 break;
1781 default:
1782 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1783 verb = kDone_Verb; // stop the loop
1784 break;
1785 }
1786 }
1787 SkDebugf("path: done %s\n", title ? title : "");
1788}
1789
reed@android.come522ca52009-11-23 20:10:41 +00001790void SkPath::dump() const {
1791 this->dump(false);
1792}
1793
1794#ifdef SK_DEBUG
1795void SkPath::validate() const {
1796 SkASSERT(this != NULL);
1797 SkASSERT((fFillType & ~3) == 0);
1798 fPts.validate();
1799 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001800
reed@android.come522ca52009-11-23 20:10:41 +00001801 if (!fBoundsIsDirty) {
1802 SkRect bounds;
1803 compute_pt_bounds(&bounds, fPts);
1804 if (fPts.count() <= 1) {
1805 // if we're empty, fBounds may be empty but translated, so we can't
1806 // necessarily compare to bounds directly
1807 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1808 // be [2, 2, 2, 2]
1809 SkASSERT(bounds.isEmpty());
1810 SkASSERT(fBounds.isEmpty());
1811 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001812 if (bounds.isEmpty()) {
1813 SkASSERT(fBounds.isEmpty());
1814 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001815 if (!fBounds.isEmpty()) {
1816 SkASSERT(fBounds.contains(bounds));
1817 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001818 }
reed@android.come522ca52009-11-23 20:10:41 +00001819 }
1820 }
reed@google.com10296cc2011-09-21 12:29:05 +00001821
1822 uint32_t mask = 0;
1823 for (int i = 0; i < fVerbs.count(); i++) {
1824 switch (fVerbs[i]) {
1825 case kLine_Verb:
1826 mask |= kLine_SegmentMask;
1827 break;
1828 case kQuad_Verb:
1829 mask |= kQuad_SegmentMask;
1830 break;
1831 case kCubic_Verb:
1832 mask |= kCubic_SegmentMask;
1833 }
1834 }
1835 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001836}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837#endif
reed@android.come522ca52009-11-23 20:10:41 +00001838
reed@google.com04863fa2011-05-15 04:08:24 +00001839///////////////////////////////////////////////////////////////////////////////
1840
reed@google.com0b7b9822011-05-16 12:29:27 +00001841static int sign(SkScalar x) { return x < 0; }
1842#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001843
reed@google.com04863fa2011-05-15 04:08:24 +00001844static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001845 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001846}
1847
1848// only valid for a single contour
1849struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001850 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001851 fSign = 0;
1852 // warnings
1853 fCurrPt.set(0, 0);
1854 fVec0.set(0, 0);
1855 fVec1.set(0, 0);
1856 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001857
1858 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001859 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001860 }
1861
1862 SkPath::Convexity getConvexity() const { return fConvexity; }
1863
1864 void addPt(const SkPoint& pt) {
1865 if (SkPath::kConcave_Convexity == fConvexity) {
1866 return;
1867 }
1868
1869 if (0 == fPtCount) {
1870 fCurrPt = pt;
1871 ++fPtCount;
1872 } else {
1873 SkVector vec = pt - fCurrPt;
1874 if (vec.fX || vec.fY) {
1875 fCurrPt = pt;
1876 if (++fPtCount == 2) {
1877 fFirstVec = fVec1 = vec;
1878 } else {
1879 SkASSERT(fPtCount > 2);
1880 this->addVec(vec);
1881 }
reed@google.com85b6e392011-05-15 20:25:17 +00001882
1883 int sx = sign(vec.fX);
1884 int sy = sign(vec.fY);
1885 fDx += (sx != fSx);
1886 fDy += (sy != fSy);
1887 fSx = sx;
1888 fSy = sy;
1889
1890 if (fDx > 3 || fDy > 3) {
1891 fConvexity = SkPath::kConcave_Convexity;
1892 }
reed@google.com04863fa2011-05-15 04:08:24 +00001893 }
1894 }
1895 }
1896
1897 void close() {
1898 if (fPtCount > 2) {
1899 this->addVec(fFirstVec);
1900 }
1901 }
1902
1903private:
1904 void addVec(const SkVector& vec) {
1905 SkASSERT(vec.fX || vec.fY);
1906 fVec0 = fVec1;
1907 fVec1 = vec;
1908 int sign = CrossProductSign(fVec0, fVec1);
1909 if (0 == fSign) {
1910 fSign = sign;
1911 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001912 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001913 fConvexity = SkPath::kConcave_Convexity;
1914 }
1915 }
1916 }
1917
1918 SkPoint fCurrPt;
1919 SkVector fVec0, fVec1, fFirstVec;
1920 int fPtCount; // non-degenerate points
1921 int fSign;
1922 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001923 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001924};
1925
1926SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1927 SkPoint pts[4];
1928 SkPath::Verb verb;
1929 SkPath::Iter iter(path, true);
1930
1931 int contourCount = 0;
1932 int count;
1933 Convexicator state;
1934
1935 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1936 switch (verb) {
1937 case kMove_Verb:
1938 if (++contourCount > 1) {
1939 return kConcave_Convexity;
1940 }
1941 pts[1] = pts[0];
1942 count = 1;
1943 break;
1944 case kLine_Verb: count = 1; break;
1945 case kQuad_Verb: count = 2; break;
1946 case kCubic_Verb: count = 3; break;
1947 case kClose_Verb:
1948 state.close();
1949 count = 0;
1950 break;
1951 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001952 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001953 return kConcave_Convexity;
1954 }
1955
1956 for (int i = 1; i <= count; i++) {
1957 state.addPt(pts[i]);
1958 }
1959 // early exit
1960 if (kConcave_Convexity == state.getConvexity()) {
1961 return kConcave_Convexity;
1962 }
1963 }
1964 return state.getConvexity();
1965}
reed@google.com69a99432012-01-10 18:00:10 +00001966
1967///////////////////////////////////////////////////////////////////////////////
1968
1969class ContourIter {
1970public:
1971 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1972
1973 bool done() const { return fDone; }
1974 // if !done() then these may be called
1975 int count() const { return fCurrPtCount; }
1976 const SkPoint* pts() const { return fCurrPt; }
1977 void next();
1978
1979private:
1980 int fCurrPtCount;
1981 const SkPoint* fCurrPt;
1982 const uint8_t* fCurrVerb;
1983 const uint8_t* fStopVerbs;
1984 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001985 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001986};
1987
1988ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1989 const SkTDArray<SkPoint>& pts) {
1990 fStopVerbs = verbs.begin() + verbs.count();
1991
1992 fDone = false;
1993 fCurrPt = pts.begin();
1994 fCurrVerb = verbs.begin();
1995 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001996 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001997 this->next();
1998}
1999
2000void ContourIter::next() {
2001 if (fCurrVerb >= fStopVerbs) {
2002 fDone = true;
2003 }
2004 if (fDone) {
2005 return;
2006 }
2007
2008 // skip pts of prev contour
2009 fCurrPt += fCurrPtCount;
2010
2011 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2012 int ptCount = 1; // moveTo
2013 const uint8_t* verbs = fCurrVerb;
2014
2015 for (++verbs; verbs < fStopVerbs; ++verbs) {
2016 switch (*verbs) {
2017 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002018 goto CONTOUR_END;
2019 case SkPath::kLine_Verb:
2020 ptCount += 1;
2021 break;
2022 case SkPath::kQuad_Verb:
2023 ptCount += 2;
2024 break;
2025 case SkPath::kCubic_Verb:
2026 ptCount += 3;
2027 break;
2028 default: // kClose_Verb, just keep going
2029 break;
2030 }
2031 }
2032CONTOUR_END:
2033 fCurrPtCount = ptCount;
2034 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002035 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002036}
2037
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002038// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002039static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002040 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2041 // We may get 0 when the above subtracts underflow. We expect this to be
2042 // very rare and lazily promote to double.
2043 if (0 == cross) {
2044 double p0x = SkScalarToDouble(p0.fX);
2045 double p0y = SkScalarToDouble(p0.fY);
2046
2047 double p1x = SkScalarToDouble(p1.fX);
2048 double p1y = SkScalarToDouble(p1.fY);
2049
2050 double p2x = SkScalarToDouble(p2.fX);
2051 double p2y = SkScalarToDouble(p2.fY);
2052
2053 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2054 (p1y - p0y) * (p2x - p0x));
2055
2056 }
2057 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002058}
2059
reed@google.comc1ea60a2012-01-31 15:15:36 +00002060// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002061static int find_max_y(const SkPoint pts[], int count) {
2062 SkASSERT(count > 0);
2063 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002064 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002065 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002066 SkScalar y = pts[i].fY;
2067 if (y > max) {
2068 max = y;
2069 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002070 }
2071 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002072 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002073}
2074
reed@google.comcabaf1d2012-01-11 21:03:05 +00002075static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2076 int i = index;
2077 for (;;) {
2078 i = (i + inc) % n;
2079 if (i == index) { // we wrapped around, so abort
2080 break;
2081 }
2082 if (pts[index] != pts[i]) { // found a different point, success!
2083 break;
2084 }
2085 }
2086 return i;
2087}
2088
reed@google.comc1ea60a2012-01-31 15:15:36 +00002089/**
2090 * Starting at index, and moving forward (incrementing), find the xmin and
2091 * xmax of the contiguous points that have the same Y.
2092 */
2093static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2094 int* maxIndexPtr) {
2095 const SkScalar y = pts[index].fY;
2096 SkScalar min = pts[index].fX;
2097 SkScalar max = min;
2098 int minIndex = index;
2099 int maxIndex = index;
2100 for (int i = index + 1; i < n; ++i) {
2101 if (pts[i].fY != y) {
2102 break;
2103 }
2104 SkScalar x = pts[i].fX;
2105 if (x < min) {
2106 min = x;
2107 minIndex = i;
2108 } else if (x > max) {
2109 max = x;
2110 maxIndex = i;
2111 }
2112 }
2113 *maxIndexPtr = maxIndex;
2114 return minIndex;
2115}
2116
reed@google.comac8543f2012-01-30 20:51:25 +00002117static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2118 if (dir) {
2119 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2120 }
2121 return true;
2122}
2123
reed@google.comc1ea60a2012-01-31 15:15:36 +00002124#if 0
2125#include "SkString.h"
2126#include "../utils/SkParsePath.h"
2127static void dumpPath(const SkPath& path) {
2128 SkString str;
2129 SkParsePath::ToSVGString(path, &str);
2130 SkDebugf("%s\n", str.c_str());
2131}
2132#endif
2133
reed@google.comac8543f2012-01-30 20:51:25 +00002134/*
2135 * We loop through all contours, and keep the computed cross-product of the
2136 * contour that contained the global y-max. If we just look at the first
2137 * contour, we may find one that is wound the opposite way (correctly) since
2138 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2139 * that is outer most (or at least has the global y-max) before we can consider
2140 * its cross product.
2141 */
reed@google.com69a99432012-01-10 18:00:10 +00002142bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002143// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002144 // don't want to pay the cost for computing this if it
2145 // is unknown, so we don't call isConvex()
2146 const Convexity conv = this->getConvexityOrUnknown();
2147
2148 ContourIter iter(fVerbs, fPts);
2149
reed@google.comac8543f2012-01-30 20:51:25 +00002150 // initialize with our logical y-min
2151 SkScalar ymax = this->getBounds().fTop;
2152 SkScalar ymaxCross = 0;
2153
reed@google.com69a99432012-01-10 18:00:10 +00002154 for (; !iter.done(); iter.next()) {
2155 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002156 if (n < 3) {
2157 continue;
2158 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002159
reed@google.comcabaf1d2012-01-11 21:03:05 +00002160 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002161 SkScalar cross = 0;
2162 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002163 // we loop, skipping over degenerate or flat segments that will
2164 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002165 for (int i = 0; i < n - 2; ++i) {
2166 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2167 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002168 // early-exit, as kConvex is assumed to have only 1
2169 // non-degenerate contour
2170 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002171 }
2172 }
2173 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002174 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002175 if (pts[index].fY < ymax) {
2176 continue;
2177 }
2178
reed@google.comc1ea60a2012-01-31 15:15:36 +00002179 // If there is more than 1 distinct point at the y-max, we take the
2180 // x-min and x-max of them and just subtract to compute the dir.
2181 if (pts[(index + 1) % n].fY == pts[index].fY) {
2182 int maxIndex;
2183 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002184 if (minIndex == maxIndex) {
2185 goto TRY_CROSSPROD;
2186 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002187 SkASSERT(pts[minIndex].fY == pts[index].fY);
2188 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2189 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2190 // we just subtract the indices, and let that auto-convert to
2191 // SkScalar, since we just want - or + to signal the direction.
2192 cross = minIndex - maxIndex;
2193 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002194 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002195 // Find a next and prev index to use for the cross-product test,
2196 // but we try to find pts that form non-zero vectors from pts[index]
2197 //
2198 // Its possible that we can't find two non-degenerate vectors, so
2199 // we have to guard our search (e.g. all the pts could be in the
2200 // same place).
2201
2202 // we pass n - 1 instead of -1 so we don't foul up % operator by
2203 // passing it a negative LH argument.
2204 int prev = find_diff_pt(pts, index, n, n - 1);
2205 if (prev == index) {
2206 // completely degenerate, skip to next contour
2207 continue;
2208 }
2209 int next = find_diff_pt(pts, index, n, 1);
2210 SkASSERT(next != index);
2211 cross = cross_prod(pts[prev], pts[index], pts[next]);
2212 // if we get a zero, but the pts aren't on top of each other, then
2213 // we can just look at the direction
2214 if (0 == cross) {
2215 // construct the subtract so we get the correct Direction below
2216 cross = pts[index].fX - pts[next].fX;
2217 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002218 }
reed@google.comac8543f2012-01-30 20:51:25 +00002219
2220 if (cross) {
2221 // record our best guess so far
2222 ymax = pts[index].fY;
2223 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002224 }
reed@google.com69a99432012-01-10 18:00:10 +00002225 }
2226 }
reed@google.com69a99432012-01-10 18:00:10 +00002227
reed@google.comac8543f2012-01-30 20:51:25 +00002228 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2229}