blob: 5f63651d6dd7ca6218aec69ad6fa9903d1233d61 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000011#include "SkReader32.h"
12#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
14
reed@google.com744faba2012-05-29 19:54:52 +000015// This value is just made-up for now. When count is 4, calling memset was much
16// slower than just writing the loop. This seems odd, and hopefully in the
17// future this we appear to have been a fluke...
18#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
19
reed@android.com8a1c16f2008-12-17 15:59:43 +000020////////////////////////////////////////////////////////////////////////////
21
reed@google.com3563c9e2011-11-14 19:34:57 +000022/**
23 * Path.bounds is defined to be the bounds of all the control points.
24 * If we called bounds.join(r) we would skip r if r was empty, which breaks
25 * our promise. Hence we have a custom joiner that doesn't look at emptiness
26 */
27static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
28 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
29 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
30 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
31 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
32}
33
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000034static bool is_degenerate(const SkPath& path) {
35 SkPath::Iter iter(path, false);
36 SkPoint pts[4];
37 return SkPath::kDone_Verb == iter.next(pts);
38}
39
bsalomon@google.com6aa29652012-04-18 13:29:52 +000040class SkAutoDisableOvalCheck {
41public:
42 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
43 fSaved = fPath->fIsOval;
44 }
45
46 ~SkAutoDisableOvalCheck() {
47 fPath->fIsOval = fSaved;
48 }
49
50private:
51 SkPath* fPath;
52 bool fSaved;
53};
54
reed@android.com8a1c16f2008-12-17 15:59:43 +000055/* This guy's constructor/destructor bracket a path editing operation. It is
56 used when we know the bounds of the amount we are going to add to the path
57 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 It captures some state about the path up front (i.e. if it already has a
60 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000061 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000062
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000063 It also notes if the path was originally degenerate, and if so, sets
64 isConvex to true. Thus it can only be used if the contour being added is
65 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 */
67class SkAutoPathBoundsUpdate {
68public:
69 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
70 this->init(path);
71 }
72
73 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
74 SkScalar right, SkScalar bottom) {
75 fRect.set(left, top, right, bottom);
76 this->init(path);
77 }
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000080 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000082 fPath->fBounds = fRect;
83 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000085 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000086 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 }
88 }
reed@google.comabf15c12011-01-18 20:35:51 +000089
reed@android.com8a1c16f2008-12-17 15:59:43 +000090private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 SkPath* fPath;
92 SkRect fRect;
93 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000094 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000095 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000100 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000103 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000104 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
107
reed@android.comd252db02009-04-01 18:31:44 +0000108static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
110 bounds->set(0, 0, 0, 0);
111 } else {
112 bounds->set(pts.begin(), pts.count());
113// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
114 }
115}
116
117////////////////////////////////////////////////////////////////////////////
118
119/*
120 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000121 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
123
124 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000125 1. If we encounter degenerate segments, remove them
126 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
127 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
128 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129*/
130
131////////////////////////////////////////////////////////////////////////////
132
reed@google.comd335d1d2012-01-12 18:17:11 +0000133// flag to require a moveTo if we begin with something else, like lineTo etc.
134#define INITIAL_LASTMOVETOINDEX_VALUE ~0
135
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000136SkPath::SkPath()
137 : fFillType(kWinding_FillType)
138 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000139 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000140 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000141 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000142 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000143#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000144 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000145 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000146#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000147}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148
149SkPath::SkPath(const SkPath& src) {
150 SkDEBUGCODE(src.validate();)
151 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000152#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000153 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000154 fGenerationID = src.fGenerationID;
155 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000156#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157}
158
159SkPath::~SkPath() {
160 SkDEBUGCODE(this->validate();)
161}
162
163SkPath& SkPath::operator=(const SkPath& src) {
164 SkDEBUGCODE(src.validate();)
165
166 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000167 fBounds = src.fBounds;
168 fPts = src.fPts;
169 fVerbs = src.fVerbs;
170 fFillType = src.fFillType;
171 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000172 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000173 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000174 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000175 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000176 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177 }
178 SkDEBUGCODE(this->validate();)
179 return *this;
180}
181
reed@android.com3abec1d2009-03-02 05:36:20 +0000182bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000183 // note: don't need to look at isConvex or bounds, since just comparing the
184 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000185
186 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
187 // since it is only a cache of info in the fVerbs, but its a fast way to
188 // notice a difference
189
reed@android.com3abec1d2009-03-02 05:36:20 +0000190 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000191 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
192 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000193}
194
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195void SkPath::swap(SkPath& other) {
196 SkASSERT(&other != NULL);
197
198 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000199 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 fPts.swap(other.fPts);
201 fVerbs.swap(other.fVerbs);
202 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000203 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000204 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000205 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000206 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000207 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000208 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 }
210}
211
djsollen@google.com56c69772011-11-08 19:00:26 +0000212#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000213uint32_t SkPath::getGenerationID() const {
214 return fGenerationID;
215}
djsollen@google.come63793a2012-03-21 15:39:03 +0000216
217const SkPath* SkPath::getSourcePath() const {
218 return fSourcePath;
219}
220
221void SkPath::setSourcePath(const SkPath* path) {
222 fSourcePath = path;
223}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000224#endif
225
reed@android.com8a1c16f2008-12-17 15:59:43 +0000226void SkPath::reset() {
227 SkDEBUGCODE(this->validate();)
228
229 fPts.reset();
230 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000231 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000232 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000233 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000234 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000235 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000236 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237}
238
239void SkPath::rewind() {
240 SkDEBUGCODE(this->validate();)
241
242 fPts.rewind();
243 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000244 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000245 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000246 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000247 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000248 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000249 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250}
251
252bool SkPath::isEmpty() const {
253 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000254 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255}
256
reed@google.com7e6c4d12012-05-10 14:05:43 +0000257bool SkPath::isLine(SkPoint line[2]) const {
258 int verbCount = fVerbs.count();
259 int ptCount = fPts.count();
260
261 if (2 == verbCount && 2 == ptCount) {
262 const uint8_t* verbs = fVerbs.begin();
263 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
264 if (line) {
265 const SkPoint* pts = fPts.begin();
266 line[0] = pts[0];
267 line[1] = pts[1];
268 }
269 return true;
270 }
271 }
272 return false;
273}
274
caryclark@google.comf1316942011-07-26 19:54:45 +0000275/*
276 Determines if path is a rect by keeping track of changes in direction
277 and looking for a loop either clockwise or counterclockwise.
278
279 The direction is computed such that:
280 0: vertical up
281 1: horizontal right
282 2: vertical down
283 3: horizontal left
284
285A rectangle cycles up/right/down/left or up/left/down/right.
286
287The test fails if:
288 The path is closed, and followed by a line.
289 A second move creates a new endpoint.
290 A diagonal line is parsed.
291 There's more than four changes of direction.
292 There's a discontinuity on the line (e.g., a move in the middle)
293 The line reverses direction.
294 The rectangle doesn't complete a cycle.
295 The path contains a quadratic or cubic.
296 The path contains fewer than four points.
297 The final point isn't equal to the first point.
298
299It's OK if the path has:
300 Several colinear line segments composing a rectangle side.
301 Single points on the rectangle side.
302
303The direction takes advantage of the corners found since opposite sides
304must travel in opposite directions.
305
306FIXME: Allow colinear quads and cubics to be treated like lines.
307FIXME: If the API passes fill-only, return true if the filled stroke
308 is a rectangle, though the caller failed to close the path.
309 */
310bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000312
caryclark@google.comf1316942011-07-26 19:54:45 +0000313 int corners = 0;
314 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000315 first.set(0, 0);
316 last.set(0, 0);
317 int firstDirection = 0;
318 int lastDirection = 0;
319 int nextDirection = 0;
320 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000321 bool autoClose = false;
322 const uint8_t* verbs = fVerbs.begin();
323 const uint8_t* verbStop = fVerbs.end();
324 const SkPoint* pts = fPts.begin();
325 while (verbs != verbStop) {
326 switch (*verbs++) {
327 case kClose_Verb:
328 pts = fPts.begin();
329 autoClose = true;
330 case kLine_Verb: {
331 SkScalar left = last.fX;
332 SkScalar top = last.fY;
333 SkScalar right = pts->fX;
334 SkScalar bottom = pts->fY;
335 ++pts;
336 if (left != right && top != bottom) {
337 return false; // diagonal
338 }
339 if (left == right && top == bottom) {
340 break; // single point on side OK
341 }
342 nextDirection = (left != right) << 0 |
343 (left < right || top < bottom) << 1;
344 if (0 == corners) {
345 firstDirection = nextDirection;
346 first = last;
347 last = pts[-1];
348 corners = 1;
349 closedOrMoved = false;
350 break;
351 }
352 if (closedOrMoved) {
353 return false; // closed followed by a line
354 }
355 closedOrMoved = autoClose;
356 if (lastDirection != nextDirection) {
357 if (++corners > 4) {
358 return false; // too many direction changes
359 }
360 }
361 last = pts[-1];
362 if (lastDirection == nextDirection) {
363 break; // colinear segment
364 }
365 // Possible values for corners are 2, 3, and 4.
366 // When corners == 3, nextDirection opposes firstDirection.
367 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000368 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000369 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
370 if ((directionCycle ^ turn) != nextDirection) {
371 return false; // direction didn't follow cycle
372 }
373 break;
374 }
375 case kQuad_Verb:
376 case kCubic_Verb:
377 return false; // quadratic, cubic not allowed
378 case kMove_Verb:
379 last = *pts++;
380 closedOrMoved = true;
381 break;
382 }
383 lastDirection = nextDirection;
384 }
385 // Success if 4 corners and first point equals last
386 bool result = 4 == corners && first == last;
387 if (result && rect) {
388 *rect = getBounds();
389 }
390 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391}
392
393int SkPath::getPoints(SkPoint copy[], int max) const {
394 SkDEBUGCODE(this->validate();)
395
396 SkASSERT(max >= 0);
397 int count = fPts.count();
398 if (copy && max > 0 && count > 0) {
399 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
400 }
401 return count;
402}
403
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000404SkPoint SkPath::getPoint(int index) const {
405 if ((unsigned)index < (unsigned)fPts.count()) {
406 return fPts[index];
407 }
408 return SkPoint::Make(0, 0);
409}
410
reed@google.com294dd7b2011-10-11 11:58:32 +0000411bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 SkDEBUGCODE(this->validate();)
413
reed@google.com294dd7b2011-10-11 11:58:32 +0000414 int count = fPts.count();
415 if (count > 0) {
416 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417 *lastPt = fPts[count - 1];
418 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000419 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000421 if (lastPt) {
422 lastPt->set(0, 0);
423 }
424 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425}
426
427void SkPath::setLastPt(SkScalar x, SkScalar y) {
428 SkDEBUGCODE(this->validate();)
429
430 int count = fPts.count();
431 if (count == 0) {
432 this->moveTo(x, y);
433 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000434 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000436 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437 }
438}
439
reed@android.comd252db02009-04-01 18:31:44 +0000440void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000442 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443
reed@android.comd252db02009-04-01 18:31:44 +0000444 fBoundsIsDirty = false;
445 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446}
447
reed@google.com04863fa2011-05-15 04:08:24 +0000448void SkPath::setConvexity(Convexity c) {
449 if (fConvexity != c) {
450 fConvexity = c;
451 GEN_ID_INC;
452 }
453}
454
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455//////////////////////////////////////////////////////////////////////////////
456// Construction methods
457
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000458#define DIRTY_AFTER_EDIT \
459 do { \
460 fBoundsIsDirty = true; \
461 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000462 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000463 } while (0)
464
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000465#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
466 do { \
467 fBoundsIsDirty = true; \
468 } while (0)
469
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470void SkPath::incReserve(U16CPU inc) {
471 SkDEBUGCODE(this->validate();)
472
473 fVerbs.setReserve(fVerbs.count() + inc);
474 fPts.setReserve(fPts.count() + inc);
475
476 SkDEBUGCODE(this->validate();)
477}
478
479void SkPath::moveTo(SkScalar x, SkScalar y) {
480 SkDEBUGCODE(this->validate();)
481
482 int vc = fVerbs.count();
483 SkPoint* pt;
484
reed@google.comd335d1d2012-01-12 18:17:11 +0000485 // remember our index
486 fLastMoveToIndex = fPts.count();
487
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000488 pt = fPts.append();
489 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490 pt->set(x, y);
491
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000492 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000493 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000494}
495
496void SkPath::rMoveTo(SkScalar x, SkScalar y) {
497 SkPoint pt;
498 this->getLastPt(&pt);
499 this->moveTo(pt.fX + x, pt.fY + y);
500}
501
reed@google.comd335d1d2012-01-12 18:17:11 +0000502void SkPath::injectMoveToIfNeeded() {
503 if (fLastMoveToIndex < 0) {
504 SkScalar x, y;
505 if (fVerbs.count() == 0) {
506 x = y = 0;
507 } else {
508 const SkPoint& pt = fPts[~fLastMoveToIndex];
509 x = pt.fX;
510 y = pt.fY;
511 }
512 this->moveTo(x, y);
513 }
514}
515
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516void SkPath::lineTo(SkScalar x, SkScalar y) {
517 SkDEBUGCODE(this->validate();)
518
reed@google.comd335d1d2012-01-12 18:17:11 +0000519 this->injectMoveToIfNeeded();
520
reed@android.com8a1c16f2008-12-17 15:59:43 +0000521 fPts.append()->set(x, y);
522 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000523 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000524
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000525 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000526 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527}
528
529void SkPath::rLineTo(SkScalar x, SkScalar y) {
530 SkPoint pt;
531 this->getLastPt(&pt);
532 this->lineTo(pt.fX + x, pt.fY + y);
533}
534
535void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
536 SkDEBUGCODE(this->validate();)
537
reed@google.comd335d1d2012-01-12 18:17:11 +0000538 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000539
540 SkPoint* pts = fPts.append(2);
541 pts[0].set(x1, y1);
542 pts[1].set(x2, y2);
543 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000544 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000546 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000547 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000548}
549
550void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
551 SkPoint pt;
552 this->getLastPt(&pt);
553 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
554}
555
556void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
557 SkScalar x3, SkScalar y3) {
558 SkDEBUGCODE(this->validate();)
559
reed@google.comd335d1d2012-01-12 18:17:11 +0000560 this->injectMoveToIfNeeded();
561
reed@android.com8a1c16f2008-12-17 15:59:43 +0000562 SkPoint* pts = fPts.append(3);
563 pts[0].set(x1, y1);
564 pts[1].set(x2, y2);
565 pts[2].set(x3, y3);
566 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000567 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000568
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000569 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000570 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000571}
572
573void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
574 SkScalar x3, SkScalar y3) {
575 SkPoint pt;
576 this->getLastPt(&pt);
577 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
578 pt.fX + x3, pt.fY + y3);
579}
580
581void SkPath::close() {
582 SkDEBUGCODE(this->validate();)
583
584 int count = fVerbs.count();
585 if (count > 0) {
586 switch (fVerbs[count - 1]) {
587 case kLine_Verb:
588 case kQuad_Verb:
589 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000590 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000591 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000592 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 break;
594 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000595 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 break;
597 }
598 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000599
600 // signal that we need a moveTo to follow us (unless we're done)
601#if 0
602 if (fLastMoveToIndex >= 0) {
603 fLastMoveToIndex = ~fLastMoveToIndex;
604 }
605#else
606 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
607#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608}
609
610///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000611
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612void SkPath::addRect(const SkRect& rect, Direction dir) {
613 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
614}
615
616void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
617 SkScalar bottom, Direction dir) {
618 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
619
620 this->incReserve(5);
621
622 this->moveTo(left, top);
623 if (dir == kCCW_Direction) {
624 this->lineTo(left, bottom);
625 this->lineTo(right, bottom);
626 this->lineTo(right, top);
627 } else {
628 this->lineTo(right, top);
629 this->lineTo(right, bottom);
630 this->lineTo(left, bottom);
631 }
632 this->close();
633}
634
reed@google.com744faba2012-05-29 19:54:52 +0000635void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
636 SkDEBUGCODE(this->validate();)
637 if (count <= 0) {
638 return;
639 }
640
641 fLastMoveToIndex = fPts.count();
642 fPts.append(count, pts);
643
644 // +close makes room for the extra kClose_Verb
645 uint8_t* vb = fVerbs.append(count + close);
646 vb[0] = kMove_Verb;
647
648 if (count > 1) {
649 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
650 // be 0, the compiler will remove the test/branch entirely.
651 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
652 memset(&vb[1], kLine_Verb, count - 1);
653 } else {
654 for (int i = 1; i < count; ++i) {
655 vb[i] = kLine_Verb;
656 }
657 }
658 fSegmentMask |= kLine_SegmentMask;
659 }
660 if (close) {
661 vb[count] = kClose_Verb;
662 }
663
664 GEN_ID_INC;
665 DIRTY_AFTER_EDIT;
666}
667
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
669
670void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
671 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 SkScalar w = rect.width();
673 SkScalar halfW = SkScalarHalf(w);
674 SkScalar h = rect.height();
675 SkScalar halfH = SkScalarHalf(h);
676
677 if (halfW <= 0 || halfH <= 0) {
678 return;
679 }
680
reed@google.comabf15c12011-01-18 20:35:51 +0000681 bool skip_hori = rx >= halfW;
682 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683
684 if (skip_hori && skip_vert) {
685 this->addOval(rect, dir);
686 return;
687 }
reed@google.comabf15c12011-01-18 20:35:51 +0000688
689 SkAutoPathBoundsUpdate apbu(this, rect);
690
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 if (skip_hori) {
692 rx = halfW;
693 } else if (skip_vert) {
694 ry = halfH;
695 }
696
697 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
698 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
699
700 this->incReserve(17);
701 this->moveTo(rect.fRight - rx, rect.fTop);
702 if (dir == kCCW_Direction) {
703 if (!skip_hori) {
704 this->lineTo(rect.fLeft + rx, rect.fTop); // top
705 }
706 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
707 rect.fLeft, rect.fTop + ry - sy,
708 rect.fLeft, rect.fTop + ry); // top-left
709 if (!skip_vert) {
710 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
711 }
712 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
713 rect.fLeft + rx - sx, rect.fBottom,
714 rect.fLeft + rx, rect.fBottom); // bot-left
715 if (!skip_hori) {
716 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
717 }
718 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
719 rect.fRight, rect.fBottom - ry + sy,
720 rect.fRight, rect.fBottom - ry); // bot-right
721 if (!skip_vert) {
722 this->lineTo(rect.fRight, rect.fTop + ry);
723 }
724 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
725 rect.fRight - rx + sx, rect.fTop,
726 rect.fRight - rx, rect.fTop); // top-right
727 } else {
728 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
729 rect.fRight, rect.fTop + ry - sy,
730 rect.fRight, rect.fTop + ry); // top-right
731 if (!skip_vert) {
732 this->lineTo(rect.fRight, rect.fBottom - ry);
733 }
734 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
735 rect.fRight - rx + sx, rect.fBottom,
736 rect.fRight - rx, rect.fBottom); // bot-right
737 if (!skip_hori) {
738 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
739 }
740 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
741 rect.fLeft, rect.fBottom - ry + sy,
742 rect.fLeft, rect.fBottom - ry); // bot-left
743 if (!skip_vert) {
744 this->lineTo(rect.fLeft, rect.fTop + ry); // left
745 }
746 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
747 rect.fLeft + rx - sx, rect.fTop,
748 rect.fLeft + rx, rect.fTop); // top-left
749 if (!skip_hori) {
750 this->lineTo(rect.fRight - rx, rect.fTop); // top
751 }
752 }
753 this->close();
754}
755
756static void add_corner_arc(SkPath* path, const SkRect& rect,
757 SkScalar rx, SkScalar ry, int startAngle,
758 SkPath::Direction dir, bool forceMoveTo) {
759 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
760 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000761
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762 SkRect r;
763 r.set(-rx, -ry, rx, ry);
764
765 switch (startAngle) {
766 case 0:
767 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
768 break;
769 case 90:
770 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
771 break;
772 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
773 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000774 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775 }
reed@google.comabf15c12011-01-18 20:35:51 +0000776
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777 SkScalar start = SkIntToScalar(startAngle);
778 SkScalar sweep = SkIntToScalar(90);
779 if (SkPath::kCCW_Direction == dir) {
780 start += sweep;
781 sweep = -sweep;
782 }
reed@google.comabf15c12011-01-18 20:35:51 +0000783
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 path->arcTo(r, start, sweep, forceMoveTo);
785}
786
787void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
788 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000789 // abort before we invoke SkAutoPathBoundsUpdate()
790 if (rect.isEmpty()) {
791 return;
792 }
793
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 SkAutoPathBoundsUpdate apbu(this, rect);
795
796 if (kCW_Direction == dir) {
797 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
798 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
799 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
800 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
801 } else {
802 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
803 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
804 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
805 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
806 }
807 this->close();
808}
809
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000810bool SkPath::hasOnlyMoveTos() const {
811 const uint8_t* verbs = fVerbs.begin();
812 const uint8_t* verbStop = fVerbs.end();
813 while (verbs != verbStop) {
814 if (*verbs == kLine_Verb ||
815 *verbs == kQuad_Verb ||
816 *verbs == kCubic_Verb) {
817 return false;
818 }
819 ++verbs;
820 }
821 return true;
822}
823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000825 /* If addOval() is called after previous moveTo(),
826 this path is still marked as an oval. This is used to
827 fit into WebKit's calling sequences.
828 We can't simply check isEmpty() in this case, as additional
829 moveTo() would mark the path non empty.
830 */
831 fIsOval = hasOnlyMoveTos();
832
833 SkAutoDisableOvalCheck adoc(this);
834
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 SkAutoPathBoundsUpdate apbu(this, oval);
836
837 SkScalar cx = oval.centerX();
838 SkScalar cy = oval.centerY();
839 SkScalar rx = SkScalarHalf(oval.width());
840 SkScalar ry = SkScalarHalf(oval.height());
841#if 0 // these seem faster than using quads (1/2 the number of edges)
842 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
843 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
844
845 this->incReserve(13);
846 this->moveTo(cx + rx, cy);
847 if (dir == kCCW_Direction) {
848 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
849 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
850 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
851 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
852 } else {
853 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
854 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
855 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
856 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
857 }
858#else
859 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
860 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
861 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
862 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
863
864 /*
865 To handle imprecision in computing the center and radii, we revert to
866 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
867 to ensure that we don't exceed the oval's bounds *ever*, since we want
868 to use oval for our fast-bounds, rather than have to recompute it.
869 */
870 const SkScalar L = oval.fLeft; // cx - rx
871 const SkScalar T = oval.fTop; // cy - ry
872 const SkScalar R = oval.fRight; // cx + rx
873 const SkScalar B = oval.fBottom; // cy + ry
874
875 this->incReserve(17); // 8 quads + close
876 this->moveTo(R, cy);
877 if (dir == kCCW_Direction) {
878 this->quadTo( R, cy - sy, cx + mx, cy - my);
879 this->quadTo(cx + sx, T, cx , T);
880 this->quadTo(cx - sx, T, cx - mx, cy - my);
881 this->quadTo( L, cy - sy, L, cy );
882 this->quadTo( L, cy + sy, cx - mx, cy + my);
883 this->quadTo(cx - sx, B, cx , B);
884 this->quadTo(cx + sx, B, cx + mx, cy + my);
885 this->quadTo( R, cy + sy, R, cy );
886 } else {
887 this->quadTo( R, cy + sy, cx + mx, cy + my);
888 this->quadTo(cx + sx, B, cx , B);
889 this->quadTo(cx - sx, B, cx - mx, cy + my);
890 this->quadTo( L, cy + sy, L, cy );
891 this->quadTo( L, cy - sy, cx - mx, cy - my);
892 this->quadTo(cx - sx, T, cx , T);
893 this->quadTo(cx + sx, T, cx + mx, cy - my);
894 this->quadTo( R, cy - sy, R, cy );
895 }
896#endif
897 this->close();
898}
899
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000900bool SkPath::isOval(SkRect* rect) const {
901 if (fIsOval && rect) {
902 *rect = getBounds();
903 }
904
905 return fIsOval;
906}
907
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
909 if (r > 0) {
910 SkRect rect;
911 rect.set(x - r, y - r, x + r, y + r);
912 this->addOval(rect, dir);
913 }
914}
915
916#include "SkGeometry.h"
917
918static int build_arc_points(const SkRect& oval, SkScalar startAngle,
919 SkScalar sweepAngle,
920 SkPoint pts[kSkBuildQuadArcStorage]) {
921 SkVector start, stop;
922
923 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
924 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
925 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000926
927 /* If the sweep angle is nearly (but less than) 360, then due to precision
928 loss in radians-conversion and/or sin/cos, we may end up with coincident
929 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
930 of drawing a nearly complete circle (good).
931 e.g. canvas.drawArc(0, 359.99, ...)
932 -vs- canvas.drawArc(0, 359.9, ...)
933 We try to detect this edge case, and tweak the stop vector
934 */
935 if (start == stop) {
936 SkScalar sw = SkScalarAbs(sweepAngle);
937 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
938 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
939 // make a guess at a tiny angle (in radians) to tweak by
940 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
941 // not sure how much will be enough, so we use a loop
942 do {
943 stopRad -= deltaRad;
944 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
945 } while (start == stop);
946 }
947 }
948
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000950
reed@android.com8a1c16f2008-12-17 15:59:43 +0000951 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
952 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000953
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 return SkBuildQuadArc(start, stop,
955 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
956 &matrix, pts);
957}
958
959void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
960 bool forceMoveTo) {
961 if (oval.width() < 0 || oval.height() < 0) {
962 return;
963 }
964
965 SkPoint pts[kSkBuildQuadArcStorage];
966 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
967 SkASSERT((count & 1) == 1);
968
969 if (fVerbs.count() == 0) {
970 forceMoveTo = true;
971 }
972 this->incReserve(count);
973 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
974 for (int i = 1; i < count; i += 2) {
975 this->quadTo(pts[i], pts[i+1]);
976 }
977}
978
979void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
980 SkScalar sweepAngle) {
981 if (oval.isEmpty() || 0 == sweepAngle) {
982 return;
983 }
984
985 const SkScalar kFullCircleAngle = SkIntToScalar(360);
986
987 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
988 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
989 return;
990 }
991
992 SkPoint pts[kSkBuildQuadArcStorage];
993 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
994
995 this->incReserve(count);
996 this->moveTo(pts[0]);
997 for (int i = 1; i < count; i += 2) {
998 this->quadTo(pts[i], pts[i+1]);
999 }
1000}
1001
1002/*
1003 Need to handle the case when the angle is sharp, and our computed end-points
1004 for the arc go behind pt1 and/or p2...
1005*/
1006void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1007 SkScalar radius) {
1008 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001009
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 // need to know our prev pt so we can construct tangent vectors
1011 {
1012 SkPoint start;
1013 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001014 // Handle degenerate cases by adding a line to the first point and
1015 // bailing out.
1016 if ((x1 == start.fX && y1 == start.fY) ||
1017 (x1 == x2 && y1 == y2) ||
1018 radius == 0) {
1019 this->lineTo(x1, y1);
1020 return;
1021 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 before.setNormalize(x1 - start.fX, y1 - start.fY);
1023 after.setNormalize(x2 - x1, y2 - y1);
1024 }
reed@google.comabf15c12011-01-18 20:35:51 +00001025
reed@android.com8a1c16f2008-12-17 15:59:43 +00001026 SkScalar cosh = SkPoint::DotProduct(before, after);
1027 SkScalar sinh = SkPoint::CrossProduct(before, after);
1028
1029 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001030 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001031 return;
1032 }
reed@google.comabf15c12011-01-18 20:35:51 +00001033
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1035 if (dist < 0) {
1036 dist = -dist;
1037 }
1038
1039 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1040 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1041 SkRotationDirection arcDir;
1042
1043 // now turn before/after into normals
1044 if (sinh > 0) {
1045 before.rotateCCW();
1046 after.rotateCCW();
1047 arcDir = kCW_SkRotationDirection;
1048 } else {
1049 before.rotateCW();
1050 after.rotateCW();
1051 arcDir = kCCW_SkRotationDirection;
1052 }
1053
1054 SkMatrix matrix;
1055 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001056
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 matrix.setScale(radius, radius);
1058 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1059 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001060
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001062
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 this->incReserve(count);
1064 // [xx,yy] == pts[0]
1065 this->lineTo(xx, yy);
1066 for (int i = 1; i < count; i += 2) {
1067 this->quadTo(pts[i], pts[i+1]);
1068 }
1069}
1070
1071///////////////////////////////////////////////////////////////////////////////
1072
1073void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1074 SkMatrix matrix;
1075
1076 matrix.setTranslate(dx, dy);
1077 this->addPath(path, matrix);
1078}
1079
1080void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1081 this->incReserve(path.fPts.count());
1082
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001083 fIsOval = false;
1084
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001085 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001086 SkPoint pts[4];
1087 Verb verb;
1088
1089 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1090
1091 while ((verb = iter.next(pts)) != kDone_Verb) {
1092 switch (verb) {
1093 case kMove_Verb:
1094 proc(matrix, &pts[0], &pts[0], 1);
1095 this->moveTo(pts[0]);
1096 break;
1097 case kLine_Verb:
1098 proc(matrix, &pts[1], &pts[1], 1);
1099 this->lineTo(pts[1]);
1100 break;
1101 case kQuad_Verb:
1102 proc(matrix, &pts[1], &pts[1], 2);
1103 this->quadTo(pts[1], pts[2]);
1104 break;
1105 case kCubic_Verb:
1106 proc(matrix, &pts[1], &pts[1], 3);
1107 this->cubicTo(pts[1], pts[2], pts[3]);
1108 break;
1109 case kClose_Verb:
1110 this->close();
1111 break;
1112 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001113 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 }
1115 }
1116}
1117
1118///////////////////////////////////////////////////////////////////////////////
1119
1120static const uint8_t gPtsInVerb[] = {
1121 1, // kMove
1122 1, // kLine
1123 2, // kQuad
1124 3, // kCubic
1125 0, // kClose
1126 0 // kDone
1127};
1128
1129// ignore the initial moveto, and stop when the 1st contour ends
1130void SkPath::pathTo(const SkPath& path) {
1131 int i, vcount = path.fVerbs.count();
1132 if (vcount == 0) {
1133 return;
1134 }
1135
1136 this->incReserve(vcount);
1137
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001138 fIsOval = false;
1139
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140 const uint8_t* verbs = path.fVerbs.begin();
1141 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1142
1143 SkASSERT(verbs[0] == kMove_Verb);
1144 for (i = 1; i < vcount; i++) {
1145 switch (verbs[i]) {
1146 case kLine_Verb:
1147 this->lineTo(pts[0].fX, pts[0].fY);
1148 break;
1149 case kQuad_Verb:
1150 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1151 break;
1152 case kCubic_Verb:
1153 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1154 pts[2].fX, pts[2].fY);
1155 break;
1156 case kClose_Verb:
1157 return;
1158 }
1159 pts += gPtsInVerb[verbs[i]];
1160 }
1161}
1162
1163// ignore the last point of the 1st contour
1164void SkPath::reversePathTo(const SkPath& path) {
1165 int i, vcount = path.fVerbs.count();
1166 if (vcount == 0) {
1167 return;
1168 }
1169
1170 this->incReserve(vcount);
1171
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172 fIsOval = false;
1173
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174 const uint8_t* verbs = path.fVerbs.begin();
1175 const SkPoint* pts = path.fPts.begin();
1176
1177 SkASSERT(verbs[0] == kMove_Verb);
1178 for (i = 1; i < vcount; i++) {
1179 int n = gPtsInVerb[verbs[i]];
1180 if (n == 0) {
1181 break;
1182 }
1183 pts += n;
1184 }
1185
1186 while (--i > 0) {
1187 switch (verbs[i]) {
1188 case kLine_Verb:
1189 this->lineTo(pts[-1].fX, pts[-1].fY);
1190 break;
1191 case kQuad_Verb:
1192 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1193 break;
1194 case kCubic_Verb:
1195 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1196 pts[-3].fX, pts[-3].fY);
1197 break;
1198 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001199 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200 break;
1201 }
1202 pts -= gPtsInVerb[verbs[i]];
1203 }
1204}
1205
reed@google.com63d73742012-01-10 15:33:12 +00001206void SkPath::reverseAddPath(const SkPath& src) {
1207 this->incReserve(src.fPts.count());
1208
1209 const SkPoint* startPts = src.fPts.begin();
1210 const SkPoint* pts = src.fPts.end();
1211 const uint8_t* startVerbs = src.fVerbs.begin();
1212 const uint8_t* verbs = src.fVerbs.end();
1213
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001214 fIsOval = false;
1215
reed@google.com63d73742012-01-10 15:33:12 +00001216 bool needMove = true;
1217 bool needClose = false;
1218 while (verbs > startVerbs) {
1219 uint8_t v = *--verbs;
1220 int n = gPtsInVerb[v];
1221
1222 if (needMove) {
1223 --pts;
1224 this->moveTo(pts->fX, pts->fY);
1225 needMove = false;
1226 }
1227 pts -= n;
1228 switch (v) {
1229 case kMove_Verb:
1230 if (needClose) {
1231 this->close();
1232 needClose = false;
1233 }
1234 needMove = true;
1235 pts += 1; // so we see the point in "if (needMove)" above
1236 break;
1237 case kLine_Verb:
1238 this->lineTo(pts[0]);
1239 break;
1240 case kQuad_Verb:
1241 this->quadTo(pts[1], pts[0]);
1242 break;
1243 case kCubic_Verb:
1244 this->cubicTo(pts[2], pts[1], pts[0]);
1245 break;
1246 case kClose_Verb:
1247 needClose = true;
1248 break;
1249 default:
1250 SkASSERT(!"unexpected verb");
1251 }
1252 }
1253}
1254
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255///////////////////////////////////////////////////////////////////////////////
1256
1257void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1258 SkMatrix matrix;
1259
1260 matrix.setTranslate(dx, dy);
1261 this->transform(matrix, dst);
1262}
1263
1264#include "SkGeometry.h"
1265
1266static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1267 int level = 2) {
1268 if (--level >= 0) {
1269 SkPoint tmp[5];
1270
1271 SkChopQuadAtHalf(pts, tmp);
1272 subdivide_quad_to(path, &tmp[0], level);
1273 subdivide_quad_to(path, &tmp[2], level);
1274 } else {
1275 path->quadTo(pts[1], pts[2]);
1276 }
1277}
1278
1279static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1280 int level = 2) {
1281 if (--level >= 0) {
1282 SkPoint tmp[7];
1283
1284 SkChopCubicAtHalf(pts, tmp);
1285 subdivide_cubic_to(path, &tmp[0], level);
1286 subdivide_cubic_to(path, &tmp[3], level);
1287 } else {
1288 path->cubicTo(pts[1], pts[2], pts[3]);
1289 }
1290}
1291
1292void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1293 SkDEBUGCODE(this->validate();)
1294 if (dst == NULL) {
1295 dst = (SkPath*)this;
1296 }
1297
tomhudson@google.com8d430182011-06-06 19:11:19 +00001298 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 SkPath tmp;
1300 tmp.fFillType = fFillType;
1301
1302 SkPath::Iter iter(*this, false);
1303 SkPoint pts[4];
1304 SkPath::Verb verb;
1305
reed@google.com4a3b7142012-05-16 17:16:46 +00001306 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307 switch (verb) {
1308 case kMove_Verb:
1309 tmp.moveTo(pts[0]);
1310 break;
1311 case kLine_Verb:
1312 tmp.lineTo(pts[1]);
1313 break;
1314 case kQuad_Verb:
1315 subdivide_quad_to(&tmp, pts);
1316 break;
1317 case kCubic_Verb:
1318 subdivide_cubic_to(&tmp, pts);
1319 break;
1320 case kClose_Verb:
1321 tmp.close();
1322 break;
1323 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001324 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325 break;
1326 }
1327 }
1328
1329 dst->swap(tmp);
1330 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1331 } else {
1332 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001333 // fBoundsIsDirty before we set it
1334 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001336 matrix.mapRect(&dst->fBounds, fBounds);
1337 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001339 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001340 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 }
1342
1343 if (this != dst) {
1344 dst->fVerbs = fVerbs;
1345 dst->fPts.setCount(fPts.count());
1346 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001347 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001348 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001349 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001351
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001353
1354 if (fIsOval) {
1355 // It's an oval only if it stays a rect.
1356 dst->fIsOval = matrix.rectStaysRect();
1357 }
1358
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 SkDEBUGCODE(dst->validate();)
1360 }
1361}
1362
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363///////////////////////////////////////////////////////////////////////////////
1364///////////////////////////////////////////////////////////////////////////////
1365
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001366enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001367 kEmptyContour_SegmentState, // The current contour is empty. We may be
1368 // starting processing or we may have just
1369 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001370 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1371 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1372 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373};
1374
1375SkPath::Iter::Iter() {
1376#ifdef SK_DEBUG
1377 fPts = NULL;
1378 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001379 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001380 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381#endif
1382 // need to init enough to make next() harmlessly return kDone_Verb
1383 fVerbs = NULL;
1384 fVerbStop = NULL;
1385 fNeedClose = false;
1386}
1387
1388SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1389 this->setPath(path, forceClose);
1390}
1391
1392void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1393 fPts = path.fPts.begin();
1394 fVerbs = path.fVerbs.begin();
1395 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001396 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001397 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 fForceClose = SkToU8(forceClose);
1399 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001400 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401}
1402
1403bool SkPath::Iter::isClosedContour() const {
1404 if (fVerbs == NULL || fVerbs == fVerbStop) {
1405 return false;
1406 }
1407 if (fForceClose) {
1408 return true;
1409 }
1410
1411 const uint8_t* verbs = fVerbs;
1412 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 if (kMove_Verb == *verbs) {
1415 verbs += 1; // skip the initial moveto
1416 }
1417
1418 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001419 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 if (kMove_Verb == v) {
1421 break;
1422 }
1423 if (kClose_Verb == v) {
1424 return true;
1425 }
1426 }
1427 return false;
1428}
1429
1430SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001431 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001433 // A special case: if both points are NaN, SkPoint::operation== returns
1434 // false, but the iterator expects that they are treated as the same.
1435 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001436 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1437 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001438 return kClose_Verb;
1439 }
1440
reed@google.com9e25dbf2012-05-15 17:05:38 +00001441 pts[0] = fLastPt;
1442 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 fLastPt = fMoveTo;
1444 fCloseLine = true;
1445 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001446 } else {
1447 pts[0] = fMoveTo;
1448 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450}
1451
reed@google.com9e25dbf2012-05-15 17:05:38 +00001452const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001453 if (fSegmentState == kAfterMove_SegmentState) {
1454 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001455 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001456 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001458 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1459 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001460 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462}
1463
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001464void SkPath::Iter::consumeDegenerateSegments() {
1465 // We need to step over anything that will not move the current draw point
1466 // forward before the next move is seen
1467 const uint8_t* lastMoveVerb = 0;
1468 const SkPoint* lastMovePt = 0;
1469 SkPoint lastPt = fLastPt;
1470 while (fVerbs != fVerbStop) {
1471 unsigned verb = *fVerbs;
1472 switch (verb) {
1473 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001474 // Keep a record of this most recent move
1475 lastMoveVerb = fVerbs;
1476 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001477 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001478 fVerbs++;
1479 fPts++;
1480 break;
1481
1482 case kClose_Verb:
1483 // A close when we are in a segment is always valid
1484 if (fSegmentState == kAfterPrimitive_SegmentState) {
1485 return;
1486 }
1487 // A close at any other time must be ignored
1488 fVerbs++;
1489 break;
1490
1491 case kLine_Verb:
1492 if (!IsLineDegenerate(lastPt, fPts[0])) {
1493 if (lastMoveVerb) {
1494 fVerbs = lastMoveVerb;
1495 fPts = lastMovePt;
1496 return;
1497 }
1498 return;
1499 }
1500 // Ignore this line and continue
1501 fVerbs++;
1502 fPts++;
1503 break;
1504
1505 case kQuad_Verb:
1506 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1507 if (lastMoveVerb) {
1508 fVerbs = lastMoveVerb;
1509 fPts = lastMovePt;
1510 return;
1511 }
1512 return;
1513 }
1514 // Ignore this line and continue
1515 fVerbs++;
1516 fPts += 2;
1517 break;
1518
1519 case kCubic_Verb:
1520 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1521 if (lastMoveVerb) {
1522 fVerbs = lastMoveVerb;
1523 fPts = lastMovePt;
1524 return;
1525 }
1526 return;
1527 }
1528 // Ignore this line and continue
1529 fVerbs++;
1530 fPts += 3;
1531 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001532
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001533 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001534 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001535 }
1536 }
1537}
1538
reed@google.com4a3b7142012-05-16 17:16:46 +00001539SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001540 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001541
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001543 // Close the curve if requested and if there is some curve to close
1544 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001545 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 return kLine_Verb;
1547 }
1548 fNeedClose = false;
1549 return kClose_Verb;
1550 }
1551 return kDone_Verb;
1552 }
1553
1554 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001555 const SkPoint* SK_RESTRICT srcPts = fPts;
1556 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557
1558 switch (verb) {
1559 case kMove_Verb:
1560 if (fNeedClose) {
1561 fVerbs -= 1;
1562 verb = this->autoClose(pts);
1563 if (verb == kClose_Verb) {
1564 fNeedClose = false;
1565 }
1566 return (Verb)verb;
1567 }
1568 if (fVerbs == fVerbStop) { // might be a trailing moveto
1569 return kDone_Verb;
1570 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001571 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001572 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001574 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001575 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 fNeedClose = fForceClose;
1577 break;
1578 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001579 pts[0] = this->cons_moveTo();
1580 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 fLastPt = srcPts[0];
1582 fCloseLine = false;
1583 srcPts += 1;
1584 break;
1585 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001586 pts[0] = this->cons_moveTo();
1587 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 fLastPt = srcPts[1];
1589 srcPts += 2;
1590 break;
1591 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001592 pts[0] = this->cons_moveTo();
1593 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 fLastPt = srcPts[2];
1595 srcPts += 3;
1596 break;
1597 case kClose_Verb:
1598 verb = this->autoClose(pts);
1599 if (verb == kLine_Verb) {
1600 fVerbs -= 1;
1601 } else {
1602 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001603 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 break;
1607 }
1608 fPts = srcPts;
1609 return (Verb)verb;
1610}
1611
1612///////////////////////////////////////////////////////////////////////////////
1613
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001614SkPath::RawIter::RawIter() {
1615#ifdef SK_DEBUG
1616 fPts = NULL;
1617 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1618#endif
1619 // need to init enough to make next() harmlessly return kDone_Verb
1620 fVerbs = NULL;
1621 fVerbStop = NULL;
1622}
1623
1624SkPath::RawIter::RawIter(const SkPath& path) {
1625 this->setPath(path);
1626}
1627
1628void SkPath::RawIter::setPath(const SkPath& path) {
1629 fPts = path.fPts.begin();
1630 fVerbs = path.fVerbs.begin();
1631 fVerbStop = path.fVerbs.end();
1632 fMoveTo.fX = fMoveTo.fY = 0;
1633 fLastPt.fX = fLastPt.fY = 0;
1634}
1635
1636SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1637 if (fVerbs == fVerbStop) {
1638 return kDone_Verb;
1639 }
1640
1641 unsigned verb = *fVerbs++;
1642 const SkPoint* srcPts = fPts;
1643
1644 switch (verb) {
1645 case kMove_Verb:
1646 if (pts) {
1647 pts[0] = *srcPts;
1648 }
1649 fMoveTo = srcPts[0];
1650 fLastPt = fMoveTo;
1651 srcPts += 1;
1652 break;
1653 case kLine_Verb:
1654 if (pts) {
1655 pts[0] = fLastPt;
1656 pts[1] = srcPts[0];
1657 }
1658 fLastPt = srcPts[0];
1659 srcPts += 1;
1660 break;
1661 case kQuad_Verb:
1662 if (pts) {
1663 pts[0] = fLastPt;
1664 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1665 }
1666 fLastPt = srcPts[1];
1667 srcPts += 2;
1668 break;
1669 case kCubic_Verb:
1670 if (pts) {
1671 pts[0] = fLastPt;
1672 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1673 }
1674 fLastPt = srcPts[2];
1675 srcPts += 3;
1676 break;
1677 case kClose_Verb:
1678 fLastPt = fMoveTo;
1679 if (pts) {
1680 pts[0] = fMoveTo;
1681 }
1682 break;
1683 }
1684 fPts = srcPts;
1685 return (Verb)verb;
1686}
1687
1688///////////////////////////////////////////////////////////////////////////////
1689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690/*
1691 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1692*/
1693
reed@google.com73945652011-04-25 19:04:27 +00001694void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 SkDEBUGCODE(this->validate();)
1696
1697 buffer.write32(fPts.count());
1698 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001699 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1701 buffer.writePad(fVerbs.begin(), fVerbs.count());
1702}
1703
reed@google.com73945652011-04-25 19:04:27 +00001704void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 fPts.setCount(buffer.readS32());
1706 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001707 uint32_t packed = buffer.readS32();
1708 fFillType = packed >> 8;
1709 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1711 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001712
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001713 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001714 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715
1716 SkDEBUGCODE(this->validate();)
1717}
1718
1719///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721void SkPath::dump(bool forceClose, const char title[]) const {
1722 Iter iter(*this, forceClose);
1723 SkPoint pts[4];
1724 Verb verb;
1725
1726 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1727 title ? title : "");
1728
reed@google.com4a3b7142012-05-16 17:16:46 +00001729 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 switch (verb) {
1731 case kMove_Verb:
1732#ifdef SK_CAN_USE_FLOAT
1733 SkDebugf(" path: moveTo [%g %g]\n",
1734 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1735#else
1736 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1737#endif
1738 break;
1739 case kLine_Verb:
1740#ifdef SK_CAN_USE_FLOAT
1741 SkDebugf(" path: lineTo [%g %g]\n",
1742 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1743#else
1744 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1745#endif
1746 break;
1747 case kQuad_Verb:
1748#ifdef SK_CAN_USE_FLOAT
1749 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1750 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1751 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1752#else
1753 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1754 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1755#endif
1756 break;
1757 case kCubic_Verb:
1758#ifdef SK_CAN_USE_FLOAT
1759 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1760 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1761 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1762 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1763#else
1764 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1765 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1766 pts[3].fX, pts[3].fY);
1767#endif
1768 break;
1769 case kClose_Verb:
1770 SkDebugf(" path: close\n");
1771 break;
1772 default:
1773 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1774 verb = kDone_Verb; // stop the loop
1775 break;
1776 }
1777 }
1778 SkDebugf("path: done %s\n", title ? title : "");
1779}
1780
reed@android.come522ca52009-11-23 20:10:41 +00001781void SkPath::dump() const {
1782 this->dump(false);
1783}
1784
1785#ifdef SK_DEBUG
1786void SkPath::validate() const {
1787 SkASSERT(this != NULL);
1788 SkASSERT((fFillType & ~3) == 0);
1789 fPts.validate();
1790 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001791
reed@android.come522ca52009-11-23 20:10:41 +00001792 if (!fBoundsIsDirty) {
1793 SkRect bounds;
1794 compute_pt_bounds(&bounds, fPts);
1795 if (fPts.count() <= 1) {
1796 // if we're empty, fBounds may be empty but translated, so we can't
1797 // necessarily compare to bounds directly
1798 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1799 // be [2, 2, 2, 2]
1800 SkASSERT(bounds.isEmpty());
1801 SkASSERT(fBounds.isEmpty());
1802 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001803 if (bounds.isEmpty()) {
1804 SkASSERT(fBounds.isEmpty());
1805 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001806 if (!fBounds.isEmpty()) {
1807 SkASSERT(fBounds.contains(bounds));
1808 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001809 }
reed@android.come522ca52009-11-23 20:10:41 +00001810 }
1811 }
reed@google.com10296cc2011-09-21 12:29:05 +00001812
1813 uint32_t mask = 0;
1814 for (int i = 0; i < fVerbs.count(); i++) {
1815 switch (fVerbs[i]) {
1816 case kLine_Verb:
1817 mask |= kLine_SegmentMask;
1818 break;
1819 case kQuad_Verb:
1820 mask |= kQuad_SegmentMask;
1821 break;
1822 case kCubic_Verb:
1823 mask |= kCubic_SegmentMask;
1824 }
1825 }
1826 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001827}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828#endif
reed@android.come522ca52009-11-23 20:10:41 +00001829
reed@google.com04863fa2011-05-15 04:08:24 +00001830///////////////////////////////////////////////////////////////////////////////
1831
reed@google.com0b7b9822011-05-16 12:29:27 +00001832static int sign(SkScalar x) { return x < 0; }
1833#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001834
reed@google.com04863fa2011-05-15 04:08:24 +00001835static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001836 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001837}
1838
1839// only valid for a single contour
1840struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001841 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001842 fSign = 0;
1843 // warnings
1844 fCurrPt.set(0, 0);
1845 fVec0.set(0, 0);
1846 fVec1.set(0, 0);
1847 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001848
1849 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001850 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001851 }
1852
1853 SkPath::Convexity getConvexity() const { return fConvexity; }
1854
1855 void addPt(const SkPoint& pt) {
1856 if (SkPath::kConcave_Convexity == fConvexity) {
1857 return;
1858 }
1859
1860 if (0 == fPtCount) {
1861 fCurrPt = pt;
1862 ++fPtCount;
1863 } else {
1864 SkVector vec = pt - fCurrPt;
1865 if (vec.fX || vec.fY) {
1866 fCurrPt = pt;
1867 if (++fPtCount == 2) {
1868 fFirstVec = fVec1 = vec;
1869 } else {
1870 SkASSERT(fPtCount > 2);
1871 this->addVec(vec);
1872 }
reed@google.com85b6e392011-05-15 20:25:17 +00001873
1874 int sx = sign(vec.fX);
1875 int sy = sign(vec.fY);
1876 fDx += (sx != fSx);
1877 fDy += (sy != fSy);
1878 fSx = sx;
1879 fSy = sy;
1880
1881 if (fDx > 3 || fDy > 3) {
1882 fConvexity = SkPath::kConcave_Convexity;
1883 }
reed@google.com04863fa2011-05-15 04:08:24 +00001884 }
1885 }
1886 }
1887
1888 void close() {
1889 if (fPtCount > 2) {
1890 this->addVec(fFirstVec);
1891 }
1892 }
1893
1894private:
1895 void addVec(const SkVector& vec) {
1896 SkASSERT(vec.fX || vec.fY);
1897 fVec0 = fVec1;
1898 fVec1 = vec;
1899 int sign = CrossProductSign(fVec0, fVec1);
1900 if (0 == fSign) {
1901 fSign = sign;
1902 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001903 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001904 fConvexity = SkPath::kConcave_Convexity;
1905 }
1906 }
1907 }
1908
1909 SkPoint fCurrPt;
1910 SkVector fVec0, fVec1, fFirstVec;
1911 int fPtCount; // non-degenerate points
1912 int fSign;
1913 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001914 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001915};
1916
1917SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1918 SkPoint pts[4];
1919 SkPath::Verb verb;
1920 SkPath::Iter iter(path, true);
1921
1922 int contourCount = 0;
1923 int count;
1924 Convexicator state;
1925
1926 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1927 switch (verb) {
1928 case kMove_Verb:
1929 if (++contourCount > 1) {
1930 return kConcave_Convexity;
1931 }
1932 pts[1] = pts[0];
1933 count = 1;
1934 break;
1935 case kLine_Verb: count = 1; break;
1936 case kQuad_Verb: count = 2; break;
1937 case kCubic_Verb: count = 3; break;
1938 case kClose_Verb:
1939 state.close();
1940 count = 0;
1941 break;
1942 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001943 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001944 return kConcave_Convexity;
1945 }
1946
1947 for (int i = 1; i <= count; i++) {
1948 state.addPt(pts[i]);
1949 }
1950 // early exit
1951 if (kConcave_Convexity == state.getConvexity()) {
1952 return kConcave_Convexity;
1953 }
1954 }
1955 return state.getConvexity();
1956}
reed@google.com69a99432012-01-10 18:00:10 +00001957
1958///////////////////////////////////////////////////////////////////////////////
1959
1960class ContourIter {
1961public:
1962 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1963
1964 bool done() const { return fDone; }
1965 // if !done() then these may be called
1966 int count() const { return fCurrPtCount; }
1967 const SkPoint* pts() const { return fCurrPt; }
1968 void next();
1969
1970private:
1971 int fCurrPtCount;
1972 const SkPoint* fCurrPt;
1973 const uint8_t* fCurrVerb;
1974 const uint8_t* fStopVerbs;
1975 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001976 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001977};
1978
1979ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1980 const SkTDArray<SkPoint>& pts) {
1981 fStopVerbs = verbs.begin() + verbs.count();
1982
1983 fDone = false;
1984 fCurrPt = pts.begin();
1985 fCurrVerb = verbs.begin();
1986 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001987 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001988 this->next();
1989}
1990
1991void ContourIter::next() {
1992 if (fCurrVerb >= fStopVerbs) {
1993 fDone = true;
1994 }
1995 if (fDone) {
1996 return;
1997 }
1998
1999 // skip pts of prev contour
2000 fCurrPt += fCurrPtCount;
2001
2002 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2003 int ptCount = 1; // moveTo
2004 const uint8_t* verbs = fCurrVerb;
2005
2006 for (++verbs; verbs < fStopVerbs; ++verbs) {
2007 switch (*verbs) {
2008 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002009 goto CONTOUR_END;
2010 case SkPath::kLine_Verb:
2011 ptCount += 1;
2012 break;
2013 case SkPath::kQuad_Verb:
2014 ptCount += 2;
2015 break;
2016 case SkPath::kCubic_Verb:
2017 ptCount += 3;
2018 break;
2019 default: // kClose_Verb, just keep going
2020 break;
2021 }
2022 }
2023CONTOUR_END:
2024 fCurrPtCount = ptCount;
2025 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002026 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002027}
2028
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002029// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002030static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002031 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2032 // We may get 0 when the above subtracts underflow. We expect this to be
2033 // very rare and lazily promote to double.
2034 if (0 == cross) {
2035 double p0x = SkScalarToDouble(p0.fX);
2036 double p0y = SkScalarToDouble(p0.fY);
2037
2038 double p1x = SkScalarToDouble(p1.fX);
2039 double p1y = SkScalarToDouble(p1.fY);
2040
2041 double p2x = SkScalarToDouble(p2.fX);
2042 double p2y = SkScalarToDouble(p2.fY);
2043
2044 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2045 (p1y - p0y) * (p2x - p0x));
2046
2047 }
2048 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002049}
2050
reed@google.comc1ea60a2012-01-31 15:15:36 +00002051// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002052static int find_max_y(const SkPoint pts[], int count) {
2053 SkASSERT(count > 0);
2054 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002055 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002056 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002057 SkScalar y = pts[i].fY;
2058 if (y > max) {
2059 max = y;
2060 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002061 }
2062 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002063 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002064}
2065
reed@google.comcabaf1d2012-01-11 21:03:05 +00002066static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2067 int i = index;
2068 for (;;) {
2069 i = (i + inc) % n;
2070 if (i == index) { // we wrapped around, so abort
2071 break;
2072 }
2073 if (pts[index] != pts[i]) { // found a different point, success!
2074 break;
2075 }
2076 }
2077 return i;
2078}
2079
reed@google.comc1ea60a2012-01-31 15:15:36 +00002080/**
2081 * Starting at index, and moving forward (incrementing), find the xmin and
2082 * xmax of the contiguous points that have the same Y.
2083 */
2084static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2085 int* maxIndexPtr) {
2086 const SkScalar y = pts[index].fY;
2087 SkScalar min = pts[index].fX;
2088 SkScalar max = min;
2089 int minIndex = index;
2090 int maxIndex = index;
2091 for (int i = index + 1; i < n; ++i) {
2092 if (pts[i].fY != y) {
2093 break;
2094 }
2095 SkScalar x = pts[i].fX;
2096 if (x < min) {
2097 min = x;
2098 minIndex = i;
2099 } else if (x > max) {
2100 max = x;
2101 maxIndex = i;
2102 }
2103 }
2104 *maxIndexPtr = maxIndex;
2105 return minIndex;
2106}
2107
reed@google.comac8543f2012-01-30 20:51:25 +00002108static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2109 if (dir) {
2110 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2111 }
2112 return true;
2113}
2114
reed@google.comc1ea60a2012-01-31 15:15:36 +00002115#if 0
2116#include "SkString.h"
2117#include "../utils/SkParsePath.h"
2118static void dumpPath(const SkPath& path) {
2119 SkString str;
2120 SkParsePath::ToSVGString(path, &str);
2121 SkDebugf("%s\n", str.c_str());
2122}
2123#endif
2124
reed@google.comac8543f2012-01-30 20:51:25 +00002125/*
2126 * We loop through all contours, and keep the computed cross-product of the
2127 * contour that contained the global y-max. If we just look at the first
2128 * contour, we may find one that is wound the opposite way (correctly) since
2129 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2130 * that is outer most (or at least has the global y-max) before we can consider
2131 * its cross product.
2132 */
reed@google.com69a99432012-01-10 18:00:10 +00002133bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002134// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002135 // don't want to pay the cost for computing this if it
2136 // is unknown, so we don't call isConvex()
2137 const Convexity conv = this->getConvexityOrUnknown();
2138
2139 ContourIter iter(fVerbs, fPts);
2140
reed@google.comac8543f2012-01-30 20:51:25 +00002141 // initialize with our logical y-min
2142 SkScalar ymax = this->getBounds().fTop;
2143 SkScalar ymaxCross = 0;
2144
reed@google.com69a99432012-01-10 18:00:10 +00002145 for (; !iter.done(); iter.next()) {
2146 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002147 if (n < 3) {
2148 continue;
2149 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002150
reed@google.comcabaf1d2012-01-11 21:03:05 +00002151 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002152 SkScalar cross = 0;
2153 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002154 // we loop, skipping over degenerate or flat segments that will
2155 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002156 for (int i = 0; i < n - 2; ++i) {
2157 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2158 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002159 // early-exit, as kConvex is assumed to have only 1
2160 // non-degenerate contour
2161 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002162 }
2163 }
2164 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002165 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002166 if (pts[index].fY < ymax) {
2167 continue;
2168 }
2169
reed@google.comc1ea60a2012-01-31 15:15:36 +00002170 // If there is more than 1 distinct point at the y-max, we take the
2171 // x-min and x-max of them and just subtract to compute the dir.
2172 if (pts[(index + 1) % n].fY == pts[index].fY) {
2173 int maxIndex;
2174 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002175 if (minIndex == maxIndex) {
2176 goto TRY_CROSSPROD;
2177 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002178 SkASSERT(pts[minIndex].fY == pts[index].fY);
2179 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2180 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2181 // we just subtract the indices, and let that auto-convert to
2182 // SkScalar, since we just want - or + to signal the direction.
2183 cross = minIndex - maxIndex;
2184 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002185 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002186 // Find a next and prev index to use for the cross-product test,
2187 // but we try to find pts that form non-zero vectors from pts[index]
2188 //
2189 // Its possible that we can't find two non-degenerate vectors, so
2190 // we have to guard our search (e.g. all the pts could be in the
2191 // same place).
2192
2193 // we pass n - 1 instead of -1 so we don't foul up % operator by
2194 // passing it a negative LH argument.
2195 int prev = find_diff_pt(pts, index, n, n - 1);
2196 if (prev == index) {
2197 // completely degenerate, skip to next contour
2198 continue;
2199 }
2200 int next = find_diff_pt(pts, index, n, 1);
2201 SkASSERT(next != index);
2202 cross = cross_prod(pts[prev], pts[index], pts[next]);
2203 // if we get a zero, but the pts aren't on top of each other, then
2204 // we can just look at the direction
2205 if (0 == cross) {
2206 // construct the subtract so we get the correct Direction below
2207 cross = pts[index].fX - pts[next].fX;
2208 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002209 }
reed@google.comac8543f2012-01-30 20:51:25 +00002210
2211 if (cross) {
2212 // record our best guess so far
2213 ymax = pts[index].fY;
2214 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002215 }
reed@google.com69a99432012-01-10 18:00:10 +00002216 }
2217 }
reed@google.com69a99432012-01-10 18:00:10 +00002218
reed@google.comac8543f2012-01-30 20:51:25 +00002219 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2220}