blob: a9693c2f67b5db3a5f6695d9a6865091794cef65 [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
reed@android.com8a1c16f2008-12-17 15:59:43 +0000482 SkPoint* pt;
483
reed@google.comd335d1d2012-01-12 18:17:11 +0000484 // remember our index
485 fLastMoveToIndex = fPts.count();
486
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000487 pt = fPts.append();
488 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489 pt->set(x, y);
490
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000491 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000492 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493}
494
495void SkPath::rMoveTo(SkScalar x, SkScalar y) {
496 SkPoint pt;
497 this->getLastPt(&pt);
498 this->moveTo(pt.fX + x, pt.fY + y);
499}
500
reed@google.comd335d1d2012-01-12 18:17:11 +0000501void SkPath::injectMoveToIfNeeded() {
502 if (fLastMoveToIndex < 0) {
503 SkScalar x, y;
504 if (fVerbs.count() == 0) {
505 x = y = 0;
506 } else {
507 const SkPoint& pt = fPts[~fLastMoveToIndex];
508 x = pt.fX;
509 y = pt.fY;
510 }
511 this->moveTo(x, y);
512 }
513}
514
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515void SkPath::lineTo(SkScalar x, SkScalar y) {
516 SkDEBUGCODE(this->validate();)
517
reed@google.comd335d1d2012-01-12 18:17:11 +0000518 this->injectMoveToIfNeeded();
519
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520 fPts.append()->set(x, y);
521 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000522 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000523
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000524 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000525 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000526}
527
528void SkPath::rLineTo(SkScalar x, SkScalar y) {
529 SkPoint pt;
530 this->getLastPt(&pt);
531 this->lineTo(pt.fX + x, pt.fY + y);
532}
533
534void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
535 SkDEBUGCODE(this->validate();)
536
reed@google.comd335d1d2012-01-12 18:17:11 +0000537 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538
539 SkPoint* pts = fPts.append(2);
540 pts[0].set(x1, y1);
541 pts[1].set(x2, y2);
542 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000543 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000545 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000546 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547}
548
549void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
550 SkPoint pt;
551 this->getLastPt(&pt);
552 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
553}
554
555void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
556 SkScalar x3, SkScalar y3) {
557 SkDEBUGCODE(this->validate();)
558
reed@google.comd335d1d2012-01-12 18:17:11 +0000559 this->injectMoveToIfNeeded();
560
reed@android.com8a1c16f2008-12-17 15:59:43 +0000561 SkPoint* pts = fPts.append(3);
562 pts[0].set(x1, y1);
563 pts[1].set(x2, y2);
564 pts[2].set(x3, y3);
565 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000566 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000567
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000568 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000569 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570}
571
572void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
573 SkScalar x3, SkScalar y3) {
574 SkPoint pt;
575 this->getLastPt(&pt);
576 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
577 pt.fX + x3, pt.fY + y3);
578}
579
580void SkPath::close() {
581 SkDEBUGCODE(this->validate();)
582
583 int count = fVerbs.count();
584 if (count > 0) {
585 switch (fVerbs[count - 1]) {
586 case kLine_Verb:
587 case kQuad_Verb:
588 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000589 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000591 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 break;
593 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000594 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000595 break;
596 }
597 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000598
599 // signal that we need a moveTo to follow us (unless we're done)
600#if 0
601 if (fLastMoveToIndex >= 0) {
602 fLastMoveToIndex = ~fLastMoveToIndex;
603 }
604#else
605 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
606#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607}
608
609///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000610
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611void SkPath::addRect(const SkRect& rect, Direction dir) {
612 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
613}
614
615void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
616 SkScalar bottom, Direction dir) {
617 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
618
619 this->incReserve(5);
620
621 this->moveTo(left, top);
622 if (dir == kCCW_Direction) {
623 this->lineTo(left, bottom);
624 this->lineTo(right, bottom);
625 this->lineTo(right, top);
626 } else {
627 this->lineTo(right, top);
628 this->lineTo(right, bottom);
629 this->lineTo(left, bottom);
630 }
631 this->close();
632}
633
reed@google.com744faba2012-05-29 19:54:52 +0000634void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
635 SkDEBUGCODE(this->validate();)
636 if (count <= 0) {
637 return;
638 }
639
640 fLastMoveToIndex = fPts.count();
641 fPts.append(count, pts);
642
643 // +close makes room for the extra kClose_Verb
644 uint8_t* vb = fVerbs.append(count + close);
645 vb[0] = kMove_Verb;
646
647 if (count > 1) {
648 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
649 // be 0, the compiler will remove the test/branch entirely.
650 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
651 memset(&vb[1], kLine_Verb, count - 1);
652 } else {
653 for (int i = 1; i < count; ++i) {
654 vb[i] = kLine_Verb;
655 }
656 }
657 fSegmentMask |= kLine_SegmentMask;
658 }
659 if (close) {
660 vb[count] = kClose_Verb;
661 }
662
663 GEN_ID_INC;
664 DIRTY_AFTER_EDIT;
665}
666
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
668
669void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
670 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671 SkScalar w = rect.width();
672 SkScalar halfW = SkScalarHalf(w);
673 SkScalar h = rect.height();
674 SkScalar halfH = SkScalarHalf(h);
675
676 if (halfW <= 0 || halfH <= 0) {
677 return;
678 }
679
reed@google.comabf15c12011-01-18 20:35:51 +0000680 bool skip_hori = rx >= halfW;
681 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682
683 if (skip_hori && skip_vert) {
684 this->addOval(rect, dir);
685 return;
686 }
reed@google.comabf15c12011-01-18 20:35:51 +0000687
688 SkAutoPathBoundsUpdate apbu(this, rect);
689
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 if (skip_hori) {
691 rx = halfW;
692 } else if (skip_vert) {
693 ry = halfH;
694 }
695
696 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
697 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
698
699 this->incReserve(17);
700 this->moveTo(rect.fRight - rx, rect.fTop);
701 if (dir == kCCW_Direction) {
702 if (!skip_hori) {
703 this->lineTo(rect.fLeft + rx, rect.fTop); // top
704 }
705 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
706 rect.fLeft, rect.fTop + ry - sy,
707 rect.fLeft, rect.fTop + ry); // top-left
708 if (!skip_vert) {
709 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
710 }
711 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
712 rect.fLeft + rx - sx, rect.fBottom,
713 rect.fLeft + rx, rect.fBottom); // bot-left
714 if (!skip_hori) {
715 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
716 }
717 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
718 rect.fRight, rect.fBottom - ry + sy,
719 rect.fRight, rect.fBottom - ry); // bot-right
720 if (!skip_vert) {
721 this->lineTo(rect.fRight, rect.fTop + ry);
722 }
723 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
724 rect.fRight - rx + sx, rect.fTop,
725 rect.fRight - rx, rect.fTop); // top-right
726 } else {
727 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
728 rect.fRight, rect.fTop + ry - sy,
729 rect.fRight, rect.fTop + ry); // top-right
730 if (!skip_vert) {
731 this->lineTo(rect.fRight, rect.fBottom - ry);
732 }
733 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
734 rect.fRight - rx + sx, rect.fBottom,
735 rect.fRight - rx, rect.fBottom); // bot-right
736 if (!skip_hori) {
737 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
738 }
739 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
740 rect.fLeft, rect.fBottom - ry + sy,
741 rect.fLeft, rect.fBottom - ry); // bot-left
742 if (!skip_vert) {
743 this->lineTo(rect.fLeft, rect.fTop + ry); // left
744 }
745 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
746 rect.fLeft + rx - sx, rect.fTop,
747 rect.fLeft + rx, rect.fTop); // top-left
748 if (!skip_hori) {
749 this->lineTo(rect.fRight - rx, rect.fTop); // top
750 }
751 }
752 this->close();
753}
754
755static void add_corner_arc(SkPath* path, const SkRect& rect,
756 SkScalar rx, SkScalar ry, int startAngle,
757 SkPath::Direction dir, bool forceMoveTo) {
758 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
759 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000760
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761 SkRect r;
762 r.set(-rx, -ry, rx, ry);
763
764 switch (startAngle) {
765 case 0:
766 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
767 break;
768 case 90:
769 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
770 break;
771 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
772 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000773 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 }
reed@google.comabf15c12011-01-18 20:35:51 +0000775
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 SkScalar start = SkIntToScalar(startAngle);
777 SkScalar sweep = SkIntToScalar(90);
778 if (SkPath::kCCW_Direction == dir) {
779 start += sweep;
780 sweep = -sweep;
781 }
reed@google.comabf15c12011-01-18 20:35:51 +0000782
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 path->arcTo(r, start, sweep, forceMoveTo);
784}
785
786void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
787 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000788 // abort before we invoke SkAutoPathBoundsUpdate()
789 if (rect.isEmpty()) {
790 return;
791 }
792
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 SkAutoPathBoundsUpdate apbu(this, rect);
794
795 if (kCW_Direction == dir) {
796 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
797 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
798 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
799 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
800 } else {
801 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
802 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
803 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
804 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
805 }
806 this->close();
807}
808
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000809bool SkPath::hasOnlyMoveTos() const {
810 const uint8_t* verbs = fVerbs.begin();
811 const uint8_t* verbStop = fVerbs.end();
812 while (verbs != verbStop) {
813 if (*verbs == kLine_Verb ||
814 *verbs == kQuad_Verb ||
815 *verbs == kCubic_Verb) {
816 return false;
817 }
818 ++verbs;
819 }
820 return true;
821}
822
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000824 /* If addOval() is called after previous moveTo(),
825 this path is still marked as an oval. This is used to
826 fit into WebKit's calling sequences.
827 We can't simply check isEmpty() in this case, as additional
828 moveTo() would mark the path non empty.
829 */
830 fIsOval = hasOnlyMoveTos();
831
832 SkAutoDisableOvalCheck adoc(this);
833
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 SkAutoPathBoundsUpdate apbu(this, oval);
835
836 SkScalar cx = oval.centerX();
837 SkScalar cy = oval.centerY();
838 SkScalar rx = SkScalarHalf(oval.width());
839 SkScalar ry = SkScalarHalf(oval.height());
840#if 0 // these seem faster than using quads (1/2 the number of edges)
841 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
842 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
843
844 this->incReserve(13);
845 this->moveTo(cx + rx, cy);
846 if (dir == kCCW_Direction) {
847 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
848 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
849 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
850 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
851 } else {
852 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
853 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
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 }
857#else
858 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
859 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
860 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
861 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
862
863 /*
864 To handle imprecision in computing the center and radii, we revert to
865 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
866 to ensure that we don't exceed the oval's bounds *ever*, since we want
867 to use oval for our fast-bounds, rather than have to recompute it.
868 */
869 const SkScalar L = oval.fLeft; // cx - rx
870 const SkScalar T = oval.fTop; // cy - ry
871 const SkScalar R = oval.fRight; // cx + rx
872 const SkScalar B = oval.fBottom; // cy + ry
873
874 this->incReserve(17); // 8 quads + close
875 this->moveTo(R, cy);
876 if (dir == kCCW_Direction) {
877 this->quadTo( R, cy - sy, cx + mx, cy - my);
878 this->quadTo(cx + sx, T, cx , T);
879 this->quadTo(cx - sx, T, cx - mx, cy - my);
880 this->quadTo( L, cy - sy, L, cy );
881 this->quadTo( L, cy + sy, cx - mx, cy + my);
882 this->quadTo(cx - sx, B, cx , B);
883 this->quadTo(cx + sx, B, cx + mx, cy + my);
884 this->quadTo( R, cy + sy, R, cy );
885 } else {
886 this->quadTo( R, cy + sy, cx + mx, cy + my);
887 this->quadTo(cx + sx, B, cx , B);
888 this->quadTo(cx - sx, B, cx - mx, cy + my);
889 this->quadTo( L, cy + sy, L, cy );
890 this->quadTo( L, cy - sy, cx - mx, cy - my);
891 this->quadTo(cx - sx, T, cx , T);
892 this->quadTo(cx + sx, T, cx + mx, cy - my);
893 this->quadTo( R, cy - sy, R, cy );
894 }
895#endif
896 this->close();
897}
898
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000899bool SkPath::isOval(SkRect* rect) const {
900 if (fIsOval && rect) {
901 *rect = getBounds();
902 }
903
904 return fIsOval;
905}
906
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
908 if (r > 0) {
909 SkRect rect;
910 rect.set(x - r, y - r, x + r, y + r);
911 this->addOval(rect, dir);
912 }
913}
914
915#include "SkGeometry.h"
916
917static int build_arc_points(const SkRect& oval, SkScalar startAngle,
918 SkScalar sweepAngle,
919 SkPoint pts[kSkBuildQuadArcStorage]) {
920 SkVector start, stop;
921
922 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
923 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
924 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000925
926 /* If the sweep angle is nearly (but less than) 360, then due to precision
927 loss in radians-conversion and/or sin/cos, we may end up with coincident
928 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
929 of drawing a nearly complete circle (good).
930 e.g. canvas.drawArc(0, 359.99, ...)
931 -vs- canvas.drawArc(0, 359.9, ...)
932 We try to detect this edge case, and tweak the stop vector
933 */
934 if (start == stop) {
935 SkScalar sw = SkScalarAbs(sweepAngle);
936 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
937 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
938 // make a guess at a tiny angle (in radians) to tweak by
939 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
940 // not sure how much will be enough, so we use a loop
941 do {
942 stopRad -= deltaRad;
943 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
944 } while (start == stop);
945 }
946 }
947
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000949
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
951 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000952
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 return SkBuildQuadArc(start, stop,
954 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
955 &matrix, pts);
956}
957
958void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
959 bool forceMoveTo) {
960 if (oval.width() < 0 || oval.height() < 0) {
961 return;
962 }
963
964 SkPoint pts[kSkBuildQuadArcStorage];
965 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
966 SkASSERT((count & 1) == 1);
967
968 if (fVerbs.count() == 0) {
969 forceMoveTo = true;
970 }
971 this->incReserve(count);
972 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
973 for (int i = 1; i < count; i += 2) {
974 this->quadTo(pts[i], pts[i+1]);
975 }
976}
977
978void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
979 SkScalar sweepAngle) {
980 if (oval.isEmpty() || 0 == sweepAngle) {
981 return;
982 }
983
984 const SkScalar kFullCircleAngle = SkIntToScalar(360);
985
986 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
987 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
988 return;
989 }
990
991 SkPoint pts[kSkBuildQuadArcStorage];
992 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
993
994 this->incReserve(count);
995 this->moveTo(pts[0]);
996 for (int i = 1; i < count; i += 2) {
997 this->quadTo(pts[i], pts[i+1]);
998 }
999}
1000
1001/*
1002 Need to handle the case when the angle is sharp, and our computed end-points
1003 for the arc go behind pt1 and/or p2...
1004*/
1005void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1006 SkScalar radius) {
1007 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001008
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009 // need to know our prev pt so we can construct tangent vectors
1010 {
1011 SkPoint start;
1012 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001013 // Handle degenerate cases by adding a line to the first point and
1014 // bailing out.
1015 if ((x1 == start.fX && y1 == start.fY) ||
1016 (x1 == x2 && y1 == y2) ||
1017 radius == 0) {
1018 this->lineTo(x1, y1);
1019 return;
1020 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 before.setNormalize(x1 - start.fX, y1 - start.fY);
1022 after.setNormalize(x2 - x1, y2 - y1);
1023 }
reed@google.comabf15c12011-01-18 20:35:51 +00001024
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 SkScalar cosh = SkPoint::DotProduct(before, after);
1026 SkScalar sinh = SkPoint::CrossProduct(before, after);
1027
1028 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001029 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 return;
1031 }
reed@google.comabf15c12011-01-18 20:35:51 +00001032
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1034 if (dist < 0) {
1035 dist = -dist;
1036 }
1037
1038 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1039 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1040 SkRotationDirection arcDir;
1041
1042 // now turn before/after into normals
1043 if (sinh > 0) {
1044 before.rotateCCW();
1045 after.rotateCCW();
1046 arcDir = kCW_SkRotationDirection;
1047 } else {
1048 before.rotateCW();
1049 after.rotateCW();
1050 arcDir = kCCW_SkRotationDirection;
1051 }
1052
1053 SkMatrix matrix;
1054 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001055
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 matrix.setScale(radius, radius);
1057 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1058 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001059
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001061
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 this->incReserve(count);
1063 // [xx,yy] == pts[0]
1064 this->lineTo(xx, yy);
1065 for (int i = 1; i < count; i += 2) {
1066 this->quadTo(pts[i], pts[i+1]);
1067 }
1068}
1069
1070///////////////////////////////////////////////////////////////////////////////
1071
1072void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1073 SkMatrix matrix;
1074
1075 matrix.setTranslate(dx, dy);
1076 this->addPath(path, matrix);
1077}
1078
1079void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1080 this->incReserve(path.fPts.count());
1081
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001082 fIsOval = false;
1083
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001084 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 SkPoint pts[4];
1086 Verb verb;
1087
1088 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1089
1090 while ((verb = iter.next(pts)) != kDone_Verb) {
1091 switch (verb) {
1092 case kMove_Verb:
1093 proc(matrix, &pts[0], &pts[0], 1);
1094 this->moveTo(pts[0]);
1095 break;
1096 case kLine_Verb:
1097 proc(matrix, &pts[1], &pts[1], 1);
1098 this->lineTo(pts[1]);
1099 break;
1100 case kQuad_Verb:
1101 proc(matrix, &pts[1], &pts[1], 2);
1102 this->quadTo(pts[1], pts[2]);
1103 break;
1104 case kCubic_Verb:
1105 proc(matrix, &pts[1], &pts[1], 3);
1106 this->cubicTo(pts[1], pts[2], pts[3]);
1107 break;
1108 case kClose_Verb:
1109 this->close();
1110 break;
1111 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001112 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113 }
1114 }
1115}
1116
1117///////////////////////////////////////////////////////////////////////////////
1118
1119static const uint8_t gPtsInVerb[] = {
1120 1, // kMove
1121 1, // kLine
1122 2, // kQuad
1123 3, // kCubic
1124 0, // kClose
1125 0 // kDone
1126};
1127
1128// ignore the initial moveto, and stop when the 1st contour ends
1129void SkPath::pathTo(const SkPath& path) {
1130 int i, vcount = path.fVerbs.count();
1131 if (vcount == 0) {
1132 return;
1133 }
1134
1135 this->incReserve(vcount);
1136
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001137 fIsOval = false;
1138
reed@android.com8a1c16f2008-12-17 15:59:43 +00001139 const uint8_t* verbs = path.fVerbs.begin();
1140 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1141
1142 SkASSERT(verbs[0] == kMove_Verb);
1143 for (i = 1; i < vcount; i++) {
1144 switch (verbs[i]) {
1145 case kLine_Verb:
1146 this->lineTo(pts[0].fX, pts[0].fY);
1147 break;
1148 case kQuad_Verb:
1149 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1150 break;
1151 case kCubic_Verb:
1152 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1153 pts[2].fX, pts[2].fY);
1154 break;
1155 case kClose_Verb:
1156 return;
1157 }
1158 pts += gPtsInVerb[verbs[i]];
1159 }
1160}
1161
1162// ignore the last point of the 1st contour
1163void SkPath::reversePathTo(const SkPath& path) {
1164 int i, vcount = path.fVerbs.count();
1165 if (vcount == 0) {
1166 return;
1167 }
1168
1169 this->incReserve(vcount);
1170
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001171 fIsOval = false;
1172
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 const uint8_t* verbs = path.fVerbs.begin();
1174 const SkPoint* pts = path.fPts.begin();
1175
1176 SkASSERT(verbs[0] == kMove_Verb);
1177 for (i = 1; i < vcount; i++) {
1178 int n = gPtsInVerb[verbs[i]];
1179 if (n == 0) {
1180 break;
1181 }
1182 pts += n;
1183 }
1184
1185 while (--i > 0) {
1186 switch (verbs[i]) {
1187 case kLine_Verb:
1188 this->lineTo(pts[-1].fX, pts[-1].fY);
1189 break;
1190 case kQuad_Verb:
1191 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1192 break;
1193 case kCubic_Verb:
1194 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1195 pts[-3].fX, pts[-3].fY);
1196 break;
1197 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001198 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 break;
1200 }
1201 pts -= gPtsInVerb[verbs[i]];
1202 }
1203}
1204
reed@google.com63d73742012-01-10 15:33:12 +00001205void SkPath::reverseAddPath(const SkPath& src) {
1206 this->incReserve(src.fPts.count());
1207
reed@google.com63d73742012-01-10 15:33:12 +00001208 const SkPoint* pts = src.fPts.end();
1209 const uint8_t* startVerbs = src.fVerbs.begin();
1210 const uint8_t* verbs = src.fVerbs.end();
1211
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001212 fIsOval = false;
1213
reed@google.com63d73742012-01-10 15:33:12 +00001214 bool needMove = true;
1215 bool needClose = false;
1216 while (verbs > startVerbs) {
1217 uint8_t v = *--verbs;
1218 int n = gPtsInVerb[v];
1219
1220 if (needMove) {
1221 --pts;
1222 this->moveTo(pts->fX, pts->fY);
1223 needMove = false;
1224 }
1225 pts -= n;
1226 switch (v) {
1227 case kMove_Verb:
1228 if (needClose) {
1229 this->close();
1230 needClose = false;
1231 }
1232 needMove = true;
1233 pts += 1; // so we see the point in "if (needMove)" above
1234 break;
1235 case kLine_Verb:
1236 this->lineTo(pts[0]);
1237 break;
1238 case kQuad_Verb:
1239 this->quadTo(pts[1], pts[0]);
1240 break;
1241 case kCubic_Verb:
1242 this->cubicTo(pts[2], pts[1], pts[0]);
1243 break;
1244 case kClose_Verb:
1245 needClose = true;
1246 break;
1247 default:
1248 SkASSERT(!"unexpected verb");
1249 }
1250 }
1251}
1252
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253///////////////////////////////////////////////////////////////////////////////
1254
1255void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1256 SkMatrix matrix;
1257
1258 matrix.setTranslate(dx, dy);
1259 this->transform(matrix, dst);
1260}
1261
1262#include "SkGeometry.h"
1263
1264static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1265 int level = 2) {
1266 if (--level >= 0) {
1267 SkPoint tmp[5];
1268
1269 SkChopQuadAtHalf(pts, tmp);
1270 subdivide_quad_to(path, &tmp[0], level);
1271 subdivide_quad_to(path, &tmp[2], level);
1272 } else {
1273 path->quadTo(pts[1], pts[2]);
1274 }
1275}
1276
1277static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1278 int level = 2) {
1279 if (--level >= 0) {
1280 SkPoint tmp[7];
1281
1282 SkChopCubicAtHalf(pts, tmp);
1283 subdivide_cubic_to(path, &tmp[0], level);
1284 subdivide_cubic_to(path, &tmp[3], level);
1285 } else {
1286 path->cubicTo(pts[1], pts[2], pts[3]);
1287 }
1288}
1289
1290void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1291 SkDEBUGCODE(this->validate();)
1292 if (dst == NULL) {
1293 dst = (SkPath*)this;
1294 }
1295
tomhudson@google.com8d430182011-06-06 19:11:19 +00001296 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 SkPath tmp;
1298 tmp.fFillType = fFillType;
1299
1300 SkPath::Iter iter(*this, false);
1301 SkPoint pts[4];
1302 SkPath::Verb verb;
1303
reed@google.com4a3b7142012-05-16 17:16:46 +00001304 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 switch (verb) {
1306 case kMove_Verb:
1307 tmp.moveTo(pts[0]);
1308 break;
1309 case kLine_Verb:
1310 tmp.lineTo(pts[1]);
1311 break;
1312 case kQuad_Verb:
1313 subdivide_quad_to(&tmp, pts);
1314 break;
1315 case kCubic_Verb:
1316 subdivide_cubic_to(&tmp, pts);
1317 break;
1318 case kClose_Verb:
1319 tmp.close();
1320 break;
1321 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001322 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 break;
1324 }
1325 }
1326
1327 dst->swap(tmp);
1328 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1329 } else {
1330 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001331 // fBoundsIsDirty before we set it
1332 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001334 matrix.mapRect(&dst->fBounds, fBounds);
1335 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001337 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001338 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 }
1340
1341 if (this != dst) {
1342 dst->fVerbs = fVerbs;
1343 dst->fPts.setCount(fPts.count());
1344 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001345 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001346 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001347 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001349
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001351
1352 if (fIsOval) {
1353 // It's an oval only if it stays a rect.
1354 dst->fIsOval = matrix.rectStaysRect();
1355 }
1356
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 SkDEBUGCODE(dst->validate();)
1358 }
1359}
1360
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361///////////////////////////////////////////////////////////////////////////////
1362///////////////////////////////////////////////////////////////////////////////
1363
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001364enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001365 kEmptyContour_SegmentState, // The current contour is empty. We may be
1366 // starting processing or we may have just
1367 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001368 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1369 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1370 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371};
1372
1373SkPath::Iter::Iter() {
1374#ifdef SK_DEBUG
1375 fPts = NULL;
1376 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001377 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001378 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379#endif
1380 // need to init enough to make next() harmlessly return kDone_Verb
1381 fVerbs = NULL;
1382 fVerbStop = NULL;
1383 fNeedClose = false;
1384}
1385
1386SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1387 this->setPath(path, forceClose);
1388}
1389
1390void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1391 fPts = path.fPts.begin();
1392 fVerbs = path.fVerbs.begin();
1393 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001394 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001395 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 fForceClose = SkToU8(forceClose);
1397 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001398 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399}
1400
1401bool SkPath::Iter::isClosedContour() const {
1402 if (fVerbs == NULL || fVerbs == fVerbStop) {
1403 return false;
1404 }
1405 if (fForceClose) {
1406 return true;
1407 }
1408
1409 const uint8_t* verbs = fVerbs;
1410 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001411
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 if (kMove_Verb == *verbs) {
1413 verbs += 1; // skip the initial moveto
1414 }
1415
1416 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001417 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 if (kMove_Verb == v) {
1419 break;
1420 }
1421 if (kClose_Verb == v) {
1422 return true;
1423 }
1424 }
1425 return false;
1426}
1427
1428SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001429 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001431 // A special case: if both points are NaN, SkPoint::operation== returns
1432 // false, but the iterator expects that they are treated as the same.
1433 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001434 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1435 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001436 return kClose_Verb;
1437 }
1438
reed@google.com9e25dbf2012-05-15 17:05:38 +00001439 pts[0] = fLastPt;
1440 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 fLastPt = fMoveTo;
1442 fCloseLine = true;
1443 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001444 } else {
1445 pts[0] = fMoveTo;
1446 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448}
1449
reed@google.com9e25dbf2012-05-15 17:05:38 +00001450const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001451 if (fSegmentState == kAfterMove_SegmentState) {
1452 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001453 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001454 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001456 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1457 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001458 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460}
1461
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001462void SkPath::Iter::consumeDegenerateSegments() {
1463 // We need to step over anything that will not move the current draw point
1464 // forward before the next move is seen
1465 const uint8_t* lastMoveVerb = 0;
1466 const SkPoint* lastMovePt = 0;
1467 SkPoint lastPt = fLastPt;
1468 while (fVerbs != fVerbStop) {
1469 unsigned verb = *fVerbs;
1470 switch (verb) {
1471 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001472 // Keep a record of this most recent move
1473 lastMoveVerb = fVerbs;
1474 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001475 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001476 fVerbs++;
1477 fPts++;
1478 break;
1479
1480 case kClose_Verb:
1481 // A close when we are in a segment is always valid
1482 if (fSegmentState == kAfterPrimitive_SegmentState) {
1483 return;
1484 }
1485 // A close at any other time must be ignored
1486 fVerbs++;
1487 break;
1488
1489 case kLine_Verb:
1490 if (!IsLineDegenerate(lastPt, fPts[0])) {
1491 if (lastMoveVerb) {
1492 fVerbs = lastMoveVerb;
1493 fPts = lastMovePt;
1494 return;
1495 }
1496 return;
1497 }
1498 // Ignore this line and continue
1499 fVerbs++;
1500 fPts++;
1501 break;
1502
1503 case kQuad_Verb:
1504 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1505 if (lastMoveVerb) {
1506 fVerbs = lastMoveVerb;
1507 fPts = lastMovePt;
1508 return;
1509 }
1510 return;
1511 }
1512 // Ignore this line and continue
1513 fVerbs++;
1514 fPts += 2;
1515 break;
1516
1517 case kCubic_Verb:
1518 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1519 if (lastMoveVerb) {
1520 fVerbs = lastMoveVerb;
1521 fPts = lastMovePt;
1522 return;
1523 }
1524 return;
1525 }
1526 // Ignore this line and continue
1527 fVerbs++;
1528 fPts += 3;
1529 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001530
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001531 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001532 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001533 }
1534 }
1535}
1536
reed@google.com4a3b7142012-05-16 17:16:46 +00001537SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001538 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001539
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001541 // Close the curve if requested and if there is some curve to close
1542 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001543 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 return kLine_Verb;
1545 }
1546 fNeedClose = false;
1547 return kClose_Verb;
1548 }
1549 return kDone_Verb;
1550 }
1551
1552 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001553 const SkPoint* SK_RESTRICT srcPts = fPts;
1554 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555
1556 switch (verb) {
1557 case kMove_Verb:
1558 if (fNeedClose) {
1559 fVerbs -= 1;
1560 verb = this->autoClose(pts);
1561 if (verb == kClose_Verb) {
1562 fNeedClose = false;
1563 }
1564 return (Verb)verb;
1565 }
1566 if (fVerbs == fVerbStop) { // might be a trailing moveto
1567 return kDone_Verb;
1568 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001569 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001570 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001572 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001573 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 fNeedClose = fForceClose;
1575 break;
1576 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001577 pts[0] = this->cons_moveTo();
1578 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 fLastPt = srcPts[0];
1580 fCloseLine = false;
1581 srcPts += 1;
1582 break;
1583 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001584 pts[0] = this->cons_moveTo();
1585 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 fLastPt = srcPts[1];
1587 srcPts += 2;
1588 break;
1589 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001590 pts[0] = this->cons_moveTo();
1591 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 fLastPt = srcPts[2];
1593 srcPts += 3;
1594 break;
1595 case kClose_Verb:
1596 verb = this->autoClose(pts);
1597 if (verb == kLine_Verb) {
1598 fVerbs -= 1;
1599 } else {
1600 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001601 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001603 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 break;
1605 }
1606 fPts = srcPts;
1607 return (Verb)verb;
1608}
1609
1610///////////////////////////////////////////////////////////////////////////////
1611
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001612SkPath::RawIter::RawIter() {
1613#ifdef SK_DEBUG
1614 fPts = NULL;
1615 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1616#endif
1617 // need to init enough to make next() harmlessly return kDone_Verb
1618 fVerbs = NULL;
1619 fVerbStop = NULL;
1620}
1621
1622SkPath::RawIter::RawIter(const SkPath& path) {
1623 this->setPath(path);
1624}
1625
1626void SkPath::RawIter::setPath(const SkPath& path) {
1627 fPts = path.fPts.begin();
1628 fVerbs = path.fVerbs.begin();
1629 fVerbStop = path.fVerbs.end();
1630 fMoveTo.fX = fMoveTo.fY = 0;
1631 fLastPt.fX = fLastPt.fY = 0;
1632}
1633
1634SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1635 if (fVerbs == fVerbStop) {
1636 return kDone_Verb;
1637 }
1638
1639 unsigned verb = *fVerbs++;
1640 const SkPoint* srcPts = fPts;
1641
1642 switch (verb) {
1643 case kMove_Verb:
1644 if (pts) {
1645 pts[0] = *srcPts;
1646 }
1647 fMoveTo = srcPts[0];
1648 fLastPt = fMoveTo;
1649 srcPts += 1;
1650 break;
1651 case kLine_Verb:
1652 if (pts) {
1653 pts[0] = fLastPt;
1654 pts[1] = srcPts[0];
1655 }
1656 fLastPt = srcPts[0];
1657 srcPts += 1;
1658 break;
1659 case kQuad_Verb:
1660 if (pts) {
1661 pts[0] = fLastPt;
1662 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1663 }
1664 fLastPt = srcPts[1];
1665 srcPts += 2;
1666 break;
1667 case kCubic_Verb:
1668 if (pts) {
1669 pts[0] = fLastPt;
1670 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1671 }
1672 fLastPt = srcPts[2];
1673 srcPts += 3;
1674 break;
1675 case kClose_Verb:
1676 fLastPt = fMoveTo;
1677 if (pts) {
1678 pts[0] = fMoveTo;
1679 }
1680 break;
1681 }
1682 fPts = srcPts;
1683 return (Verb)verb;
1684}
1685
1686///////////////////////////////////////////////////////////////////////////////
1687
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688/*
1689 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1690*/
1691
reed@google.com73945652011-04-25 19:04:27 +00001692void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693 SkDEBUGCODE(this->validate();)
1694
1695 buffer.write32(fPts.count());
1696 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001697 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1699 buffer.writePad(fVerbs.begin(), fVerbs.count());
1700}
1701
reed@google.com73945652011-04-25 19:04:27 +00001702void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 fPts.setCount(buffer.readS32());
1704 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001705 uint32_t packed = buffer.readS32();
1706 fFillType = packed >> 8;
1707 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1709 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001710
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001711 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001712 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713
1714 SkDEBUGCODE(this->validate();)
1715}
1716
1717///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719void SkPath::dump(bool forceClose, const char title[]) const {
1720 Iter iter(*this, forceClose);
1721 SkPoint pts[4];
1722 Verb verb;
1723
1724 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1725 title ? title : "");
1726
reed@google.com4a3b7142012-05-16 17:16:46 +00001727 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 switch (verb) {
1729 case kMove_Verb:
1730#ifdef SK_CAN_USE_FLOAT
1731 SkDebugf(" path: moveTo [%g %g]\n",
1732 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1733#else
1734 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1735#endif
1736 break;
1737 case kLine_Verb:
1738#ifdef SK_CAN_USE_FLOAT
1739 SkDebugf(" path: lineTo [%g %g]\n",
1740 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1741#else
1742 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1743#endif
1744 break;
1745 case kQuad_Verb:
1746#ifdef SK_CAN_USE_FLOAT
1747 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1748 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1749 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1750#else
1751 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1752 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1753#endif
1754 break;
1755 case kCubic_Verb:
1756#ifdef SK_CAN_USE_FLOAT
1757 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1758 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1759 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1760 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1761#else
1762 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1763 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1764 pts[3].fX, pts[3].fY);
1765#endif
1766 break;
1767 case kClose_Verb:
1768 SkDebugf(" path: close\n");
1769 break;
1770 default:
1771 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1772 verb = kDone_Verb; // stop the loop
1773 break;
1774 }
1775 }
1776 SkDebugf("path: done %s\n", title ? title : "");
1777}
1778
reed@android.come522ca52009-11-23 20:10:41 +00001779void SkPath::dump() const {
1780 this->dump(false);
1781}
1782
1783#ifdef SK_DEBUG
1784void SkPath::validate() const {
1785 SkASSERT(this != NULL);
1786 SkASSERT((fFillType & ~3) == 0);
1787 fPts.validate();
1788 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001789
reed@android.come522ca52009-11-23 20:10:41 +00001790 if (!fBoundsIsDirty) {
1791 SkRect bounds;
1792 compute_pt_bounds(&bounds, fPts);
1793 if (fPts.count() <= 1) {
1794 // if we're empty, fBounds may be empty but translated, so we can't
1795 // necessarily compare to bounds directly
1796 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1797 // be [2, 2, 2, 2]
1798 SkASSERT(bounds.isEmpty());
1799 SkASSERT(fBounds.isEmpty());
1800 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001801 if (bounds.isEmpty()) {
1802 SkASSERT(fBounds.isEmpty());
1803 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001804 if (!fBounds.isEmpty()) {
1805 SkASSERT(fBounds.contains(bounds));
1806 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001807 }
reed@android.come522ca52009-11-23 20:10:41 +00001808 }
1809 }
reed@google.com10296cc2011-09-21 12:29:05 +00001810
1811 uint32_t mask = 0;
1812 for (int i = 0; i < fVerbs.count(); i++) {
1813 switch (fVerbs[i]) {
1814 case kLine_Verb:
1815 mask |= kLine_SegmentMask;
1816 break;
1817 case kQuad_Verb:
1818 mask |= kQuad_SegmentMask;
1819 break;
1820 case kCubic_Verb:
1821 mask |= kCubic_SegmentMask;
1822 }
1823 }
1824 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001825}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826#endif
reed@android.come522ca52009-11-23 20:10:41 +00001827
reed@google.com04863fa2011-05-15 04:08:24 +00001828///////////////////////////////////////////////////////////////////////////////
1829
reed@google.com0b7b9822011-05-16 12:29:27 +00001830static int sign(SkScalar x) { return x < 0; }
1831#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001832
reed@google.com04863fa2011-05-15 04:08:24 +00001833static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001834 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001835}
1836
1837// only valid for a single contour
1838struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001839 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001840 fSign = 0;
1841 // warnings
1842 fCurrPt.set(0, 0);
1843 fVec0.set(0, 0);
1844 fVec1.set(0, 0);
1845 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001846
1847 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001848 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001849 }
1850
1851 SkPath::Convexity getConvexity() const { return fConvexity; }
1852
1853 void addPt(const SkPoint& pt) {
1854 if (SkPath::kConcave_Convexity == fConvexity) {
1855 return;
1856 }
1857
1858 if (0 == fPtCount) {
1859 fCurrPt = pt;
1860 ++fPtCount;
1861 } else {
1862 SkVector vec = pt - fCurrPt;
1863 if (vec.fX || vec.fY) {
1864 fCurrPt = pt;
1865 if (++fPtCount == 2) {
1866 fFirstVec = fVec1 = vec;
1867 } else {
1868 SkASSERT(fPtCount > 2);
1869 this->addVec(vec);
1870 }
reed@google.com85b6e392011-05-15 20:25:17 +00001871
1872 int sx = sign(vec.fX);
1873 int sy = sign(vec.fY);
1874 fDx += (sx != fSx);
1875 fDy += (sy != fSy);
1876 fSx = sx;
1877 fSy = sy;
1878
1879 if (fDx > 3 || fDy > 3) {
1880 fConvexity = SkPath::kConcave_Convexity;
1881 }
reed@google.com04863fa2011-05-15 04:08:24 +00001882 }
1883 }
1884 }
1885
1886 void close() {
1887 if (fPtCount > 2) {
1888 this->addVec(fFirstVec);
1889 }
1890 }
1891
1892private:
1893 void addVec(const SkVector& vec) {
1894 SkASSERT(vec.fX || vec.fY);
1895 fVec0 = fVec1;
1896 fVec1 = vec;
1897 int sign = CrossProductSign(fVec0, fVec1);
1898 if (0 == fSign) {
1899 fSign = sign;
1900 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001901 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001902 fConvexity = SkPath::kConcave_Convexity;
1903 }
1904 }
1905 }
1906
1907 SkPoint fCurrPt;
1908 SkVector fVec0, fVec1, fFirstVec;
1909 int fPtCount; // non-degenerate points
1910 int fSign;
1911 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001912 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001913};
1914
1915SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1916 SkPoint pts[4];
1917 SkPath::Verb verb;
1918 SkPath::Iter iter(path, true);
1919
1920 int contourCount = 0;
1921 int count;
1922 Convexicator state;
1923
1924 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1925 switch (verb) {
1926 case kMove_Verb:
1927 if (++contourCount > 1) {
1928 return kConcave_Convexity;
1929 }
1930 pts[1] = pts[0];
1931 count = 1;
1932 break;
1933 case kLine_Verb: count = 1; break;
1934 case kQuad_Verb: count = 2; break;
1935 case kCubic_Verb: count = 3; break;
1936 case kClose_Verb:
1937 state.close();
1938 count = 0;
1939 break;
1940 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001941 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001942 return kConcave_Convexity;
1943 }
1944
1945 for (int i = 1; i <= count; i++) {
1946 state.addPt(pts[i]);
1947 }
1948 // early exit
1949 if (kConcave_Convexity == state.getConvexity()) {
1950 return kConcave_Convexity;
1951 }
1952 }
1953 return state.getConvexity();
1954}
reed@google.com69a99432012-01-10 18:00:10 +00001955
1956///////////////////////////////////////////////////////////////////////////////
1957
1958class ContourIter {
1959public:
1960 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1961
1962 bool done() const { return fDone; }
1963 // if !done() then these may be called
1964 int count() const { return fCurrPtCount; }
1965 const SkPoint* pts() const { return fCurrPt; }
1966 void next();
1967
1968private:
1969 int fCurrPtCount;
1970 const SkPoint* fCurrPt;
1971 const uint8_t* fCurrVerb;
1972 const uint8_t* fStopVerbs;
1973 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001974 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001975};
1976
1977ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1978 const SkTDArray<SkPoint>& pts) {
1979 fStopVerbs = verbs.begin() + verbs.count();
1980
1981 fDone = false;
1982 fCurrPt = pts.begin();
1983 fCurrVerb = verbs.begin();
1984 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001985 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001986 this->next();
1987}
1988
1989void ContourIter::next() {
1990 if (fCurrVerb >= fStopVerbs) {
1991 fDone = true;
1992 }
1993 if (fDone) {
1994 return;
1995 }
1996
1997 // skip pts of prev contour
1998 fCurrPt += fCurrPtCount;
1999
2000 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2001 int ptCount = 1; // moveTo
2002 const uint8_t* verbs = fCurrVerb;
2003
2004 for (++verbs; verbs < fStopVerbs; ++verbs) {
2005 switch (*verbs) {
2006 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002007 goto CONTOUR_END;
2008 case SkPath::kLine_Verb:
2009 ptCount += 1;
2010 break;
2011 case SkPath::kQuad_Verb:
2012 ptCount += 2;
2013 break;
2014 case SkPath::kCubic_Verb:
2015 ptCount += 3;
2016 break;
2017 default: // kClose_Verb, just keep going
2018 break;
2019 }
2020 }
2021CONTOUR_END:
2022 fCurrPtCount = ptCount;
2023 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002024 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002025}
2026
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002027// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002028static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002029 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2030 // We may get 0 when the above subtracts underflow. We expect this to be
2031 // very rare and lazily promote to double.
2032 if (0 == cross) {
2033 double p0x = SkScalarToDouble(p0.fX);
2034 double p0y = SkScalarToDouble(p0.fY);
2035
2036 double p1x = SkScalarToDouble(p1.fX);
2037 double p1y = SkScalarToDouble(p1.fY);
2038
2039 double p2x = SkScalarToDouble(p2.fX);
2040 double p2y = SkScalarToDouble(p2.fY);
2041
2042 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2043 (p1y - p0y) * (p2x - p0x));
2044
2045 }
2046 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002047}
2048
reed@google.comc1ea60a2012-01-31 15:15:36 +00002049// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002050static int find_max_y(const SkPoint pts[], int count) {
2051 SkASSERT(count > 0);
2052 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002053 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002054 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002055 SkScalar y = pts[i].fY;
2056 if (y > max) {
2057 max = y;
2058 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002059 }
2060 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002061 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002062}
2063
reed@google.comcabaf1d2012-01-11 21:03:05 +00002064static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2065 int i = index;
2066 for (;;) {
2067 i = (i + inc) % n;
2068 if (i == index) { // we wrapped around, so abort
2069 break;
2070 }
2071 if (pts[index] != pts[i]) { // found a different point, success!
2072 break;
2073 }
2074 }
2075 return i;
2076}
2077
reed@google.comc1ea60a2012-01-31 15:15:36 +00002078/**
2079 * Starting at index, and moving forward (incrementing), find the xmin and
2080 * xmax of the contiguous points that have the same Y.
2081 */
2082static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2083 int* maxIndexPtr) {
2084 const SkScalar y = pts[index].fY;
2085 SkScalar min = pts[index].fX;
2086 SkScalar max = min;
2087 int minIndex = index;
2088 int maxIndex = index;
2089 for (int i = index + 1; i < n; ++i) {
2090 if (pts[i].fY != y) {
2091 break;
2092 }
2093 SkScalar x = pts[i].fX;
2094 if (x < min) {
2095 min = x;
2096 minIndex = i;
2097 } else if (x > max) {
2098 max = x;
2099 maxIndex = i;
2100 }
2101 }
2102 *maxIndexPtr = maxIndex;
2103 return minIndex;
2104}
2105
reed@google.comac8543f2012-01-30 20:51:25 +00002106static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2107 if (dir) {
2108 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2109 }
2110 return true;
2111}
2112
reed@google.comc1ea60a2012-01-31 15:15:36 +00002113#if 0
2114#include "SkString.h"
2115#include "../utils/SkParsePath.h"
2116static void dumpPath(const SkPath& path) {
2117 SkString str;
2118 SkParsePath::ToSVGString(path, &str);
2119 SkDebugf("%s\n", str.c_str());
2120}
2121#endif
2122
reed@google.comac8543f2012-01-30 20:51:25 +00002123/*
2124 * We loop through all contours, and keep the computed cross-product of the
2125 * contour that contained the global y-max. If we just look at the first
2126 * contour, we may find one that is wound the opposite way (correctly) since
2127 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2128 * that is outer most (or at least has the global y-max) before we can consider
2129 * its cross product.
2130 */
reed@google.com69a99432012-01-10 18:00:10 +00002131bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002132// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002133 // don't want to pay the cost for computing this if it
2134 // is unknown, so we don't call isConvex()
2135 const Convexity conv = this->getConvexityOrUnknown();
2136
2137 ContourIter iter(fVerbs, fPts);
2138
reed@google.comac8543f2012-01-30 20:51:25 +00002139 // initialize with our logical y-min
2140 SkScalar ymax = this->getBounds().fTop;
2141 SkScalar ymaxCross = 0;
2142
reed@google.com69a99432012-01-10 18:00:10 +00002143 for (; !iter.done(); iter.next()) {
2144 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002145 if (n < 3) {
2146 continue;
2147 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002148
reed@google.comcabaf1d2012-01-11 21:03:05 +00002149 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002150 SkScalar cross = 0;
2151 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002152 // we loop, skipping over degenerate or flat segments that will
2153 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002154 for (int i = 0; i < n - 2; ++i) {
2155 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2156 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002157 // early-exit, as kConvex is assumed to have only 1
2158 // non-degenerate contour
2159 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002160 }
2161 }
2162 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002163 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002164 if (pts[index].fY < ymax) {
2165 continue;
2166 }
2167
reed@google.comc1ea60a2012-01-31 15:15:36 +00002168 // If there is more than 1 distinct point at the y-max, we take the
2169 // x-min and x-max of them and just subtract to compute the dir.
2170 if (pts[(index + 1) % n].fY == pts[index].fY) {
2171 int maxIndex;
2172 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002173 if (minIndex == maxIndex) {
2174 goto TRY_CROSSPROD;
2175 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002176 SkASSERT(pts[minIndex].fY == pts[index].fY);
2177 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2178 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2179 // we just subtract the indices, and let that auto-convert to
2180 // SkScalar, since we just want - or + to signal the direction.
2181 cross = minIndex - maxIndex;
2182 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002183 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002184 // Find a next and prev index to use for the cross-product test,
2185 // but we try to find pts that form non-zero vectors from pts[index]
2186 //
2187 // Its possible that we can't find two non-degenerate vectors, so
2188 // we have to guard our search (e.g. all the pts could be in the
2189 // same place).
2190
2191 // we pass n - 1 instead of -1 so we don't foul up % operator by
2192 // passing it a negative LH argument.
2193 int prev = find_diff_pt(pts, index, n, n - 1);
2194 if (prev == index) {
2195 // completely degenerate, skip to next contour
2196 continue;
2197 }
2198 int next = find_diff_pt(pts, index, n, 1);
2199 SkASSERT(next != index);
2200 cross = cross_prod(pts[prev], pts[index], pts[next]);
2201 // if we get a zero, but the pts aren't on top of each other, then
2202 // we can just look at the direction
2203 if (0 == cross) {
2204 // construct the subtract so we get the correct Direction below
2205 cross = pts[index].fX - pts[next].fX;
2206 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002207 }
reed@google.comac8543f2012-01-30 20:51:25 +00002208
2209 if (cross) {
2210 // record our best guess so far
2211 ymax = pts[index].fY;
2212 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002213 }
reed@google.com69a99432012-01-10 18:00:10 +00002214 }
2215 }
reed@google.com69a99432012-01-10 18:00:10 +00002216
reed@google.comac8543f2012-01-30 20:51:25 +00002217 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2218}