blob: 4b3505a1606bd41d0eb1699e8c41c7e02baee64d [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
15////////////////////////////////////////////////////////////////////////////
16
reed@google.com3563c9e2011-11-14 19:34:57 +000017/**
18 * Path.bounds is defined to be the bounds of all the control points.
19 * If we called bounds.join(r) we would skip r if r was empty, which breaks
20 * our promise. Hence we have a custom joiner that doesn't look at emptiness
21 */
22static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
23 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
24 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
25 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
26 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
27}
28
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000029static bool is_degenerate(const SkPath& path) {
30 SkPath::Iter iter(path, false);
31 SkPoint pts[4];
32 return SkPath::kDone_Verb == iter.next(pts);
33}
34
reed@android.com8a1c16f2008-12-17 15:59:43 +000035/* This guy's constructor/destructor bracket a path editing operation. It is
36 used when we know the bounds of the amount we are going to add to the path
37 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000038
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 It captures some state about the path up front (i.e. if it already has a
40 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000041 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000042
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043 It also notes if the path was originally degenerate, and if so, sets
44 isConvex to true. Thus it can only be used if the contour being added is
45 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000046 */
47class SkAutoPathBoundsUpdate {
48public:
49 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
50 this->init(path);
51 }
52
53 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
54 SkScalar right, SkScalar bottom) {
55 fRect.set(left, top, right, bottom);
56 this->init(path);
57 }
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000060 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000062 fPath->fBounds = fRect;
63 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000065 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000066 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 }
68 }
reed@google.comabf15c12011-01-18 20:35:51 +000069
reed@android.com8a1c16f2008-12-17 15:59:43 +000070private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000071 SkPath* fPath;
72 SkRect fRect;
73 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000074 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000075 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000078 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000080 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000081 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000083 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000084 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 }
86};
87
reed@android.comd252db02009-04-01 18:31:44 +000088static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
90 bounds->set(0, 0, 0, 0);
91 } else {
92 bounds->set(pts.begin(), pts.count());
93// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
94 }
95}
96
97////////////////////////////////////////////////////////////////////////////
98
99/*
100 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000101 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
103
104 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000105 1. If we encounter degenerate segments, remove them
106 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
107 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
108 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109*/
110
111////////////////////////////////////////////////////////////////////////////
112
reed@google.comd335d1d2012-01-12 18:17:11 +0000113// flag to require a moveTo if we begin with something else, like lineTo etc.
114#define INITIAL_LASTMOVETOINDEX_VALUE ~0
115
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000116SkPath::SkPath()
117 : fFillType(kWinding_FillType)
118 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000119 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000120 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000121 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
djsollen@google.com56c69772011-11-08 19:00:26 +0000122#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000123 fGenerationID = 0;
124#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000125}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126
127SkPath::SkPath(const SkPath& src) {
128 SkDEBUGCODE(src.validate();)
129 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000130#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000131 // the assignment operator above increments the ID so correct for that here
132 fGenerationID--;
133#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134}
135
136SkPath::~SkPath() {
137 SkDEBUGCODE(this->validate();)
138}
139
140SkPath& SkPath::operator=(const SkPath& src) {
141 SkDEBUGCODE(src.validate();)
142
143 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000144 fBounds = src.fBounds;
145 fPts = src.fPts;
146 fVerbs = src.fVerbs;
147 fFillType = src.fFillType;
148 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000149 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000150 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000151 fLastMoveToIndex = src.fLastMoveToIndex;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 }
154 SkDEBUGCODE(this->validate();)
155 return *this;
156}
157
reed@android.com3abec1d2009-03-02 05:36:20 +0000158bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000159 // note: don't need to look at isConvex or bounds, since just comparing the
160 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000161
162 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
163 // since it is only a cache of info in the fVerbs, but its a fast way to
164 // notice a difference
165
reed@android.com3abec1d2009-03-02 05:36:20 +0000166 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000167 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
168 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000169}
170
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171void SkPath::swap(SkPath& other) {
172 SkASSERT(&other != NULL);
173
174 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000175 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 fPts.swap(other.fPts);
177 fVerbs.swap(other.fVerbs);
178 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000179 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000180 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000181 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000182 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000183 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 }
185}
186
djsollen@google.com56c69772011-11-08 19:00:26 +0000187#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000188uint32_t SkPath::getGenerationID() const {
189 return fGenerationID;
190}
191#endif
192
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193void SkPath::reset() {
194 SkDEBUGCODE(this->validate();)
195
196 fPts.reset();
197 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000198 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000199 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000200 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000201 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000202 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203}
204
205void SkPath::rewind() {
206 SkDEBUGCODE(this->validate();)
207
208 fPts.rewind();
209 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000210 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000211 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000212 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000213 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000214 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215}
216
217bool SkPath::isEmpty() const {
218 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000219 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220}
221
caryclark@google.comf1316942011-07-26 19:54:45 +0000222/*
223 Determines if path is a rect by keeping track of changes in direction
224 and looking for a loop either clockwise or counterclockwise.
225
226 The direction is computed such that:
227 0: vertical up
228 1: horizontal right
229 2: vertical down
230 3: horizontal left
231
232A rectangle cycles up/right/down/left or up/left/down/right.
233
234The test fails if:
235 The path is closed, and followed by a line.
236 A second move creates a new endpoint.
237 A diagonal line is parsed.
238 There's more than four changes of direction.
239 There's a discontinuity on the line (e.g., a move in the middle)
240 The line reverses direction.
241 The rectangle doesn't complete a cycle.
242 The path contains a quadratic or cubic.
243 The path contains fewer than four points.
244 The final point isn't equal to the first point.
245
246It's OK if the path has:
247 Several colinear line segments composing a rectangle side.
248 Single points on the rectangle side.
249
250The direction takes advantage of the corners found since opposite sides
251must travel in opposite directions.
252
253FIXME: Allow colinear quads and cubics to be treated like lines.
254FIXME: If the API passes fill-only, return true if the filled stroke
255 is a rectangle, though the caller failed to close the path.
256 */
257bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000259
caryclark@google.comf1316942011-07-26 19:54:45 +0000260 int corners = 0;
261 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000262 first.set(0, 0);
263 last.set(0, 0);
264 int firstDirection = 0;
265 int lastDirection = 0;
266 int nextDirection = 0;
267 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000268 bool autoClose = false;
269 const uint8_t* verbs = fVerbs.begin();
270 const uint8_t* verbStop = fVerbs.end();
271 const SkPoint* pts = fPts.begin();
272 while (verbs != verbStop) {
273 switch (*verbs++) {
274 case kClose_Verb:
275 pts = fPts.begin();
276 autoClose = true;
277 case kLine_Verb: {
278 SkScalar left = last.fX;
279 SkScalar top = last.fY;
280 SkScalar right = pts->fX;
281 SkScalar bottom = pts->fY;
282 ++pts;
283 if (left != right && top != bottom) {
284 return false; // diagonal
285 }
286 if (left == right && top == bottom) {
287 break; // single point on side OK
288 }
289 nextDirection = (left != right) << 0 |
290 (left < right || top < bottom) << 1;
291 if (0 == corners) {
292 firstDirection = nextDirection;
293 first = last;
294 last = pts[-1];
295 corners = 1;
296 closedOrMoved = false;
297 break;
298 }
299 if (closedOrMoved) {
300 return false; // closed followed by a line
301 }
302 closedOrMoved = autoClose;
303 if (lastDirection != nextDirection) {
304 if (++corners > 4) {
305 return false; // too many direction changes
306 }
307 }
308 last = pts[-1];
309 if (lastDirection == nextDirection) {
310 break; // colinear segment
311 }
312 // Possible values for corners are 2, 3, and 4.
313 // When corners == 3, nextDirection opposes firstDirection.
314 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000315 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000316 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
317 if ((directionCycle ^ turn) != nextDirection) {
318 return false; // direction didn't follow cycle
319 }
320 break;
321 }
322 case kQuad_Verb:
323 case kCubic_Verb:
324 return false; // quadratic, cubic not allowed
325 case kMove_Verb:
326 last = *pts++;
327 closedOrMoved = true;
328 break;
329 }
330 lastDirection = nextDirection;
331 }
332 // Success if 4 corners and first point equals last
333 bool result = 4 == corners && first == last;
334 if (result && rect) {
335 *rect = getBounds();
336 }
337 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338}
339
340int SkPath::getPoints(SkPoint copy[], int max) const {
341 SkDEBUGCODE(this->validate();)
342
343 SkASSERT(max >= 0);
344 int count = fPts.count();
345 if (copy && max > 0 && count > 0) {
346 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
347 }
348 return count;
349}
350
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000351SkPoint SkPath::getPoint(int index) const {
352 if ((unsigned)index < (unsigned)fPts.count()) {
353 return fPts[index];
354 }
355 return SkPoint::Make(0, 0);
356}
357
reed@google.com294dd7b2011-10-11 11:58:32 +0000358bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 SkDEBUGCODE(this->validate();)
360
reed@google.com294dd7b2011-10-11 11:58:32 +0000361 int count = fPts.count();
362 if (count > 0) {
363 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 *lastPt = fPts[count - 1];
365 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000366 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000368 if (lastPt) {
369 lastPt->set(0, 0);
370 }
371 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
374void SkPath::setLastPt(SkScalar x, SkScalar y) {
375 SkDEBUGCODE(this->validate();)
376
377 int count = fPts.count();
378 if (count == 0) {
379 this->moveTo(x, y);
380 } else {
381 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000382 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 }
384}
385
reed@android.comd252db02009-04-01 18:31:44 +0000386void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000388 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389
reed@android.comd252db02009-04-01 18:31:44 +0000390 fBoundsIsDirty = false;
391 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392}
393
reed@google.com04863fa2011-05-15 04:08:24 +0000394void SkPath::setConvexity(Convexity c) {
395 if (fConvexity != c) {
396 fConvexity = c;
397 GEN_ID_INC;
398 }
399}
400
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401//////////////////////////////////////////////////////////////////////////////
402// Construction methods
403
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000404#define DIRTY_AFTER_EDIT \
405 do { \
406 fBoundsIsDirty = true; \
407 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000408 } while (0)
409
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000410#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
411 do { \
412 fBoundsIsDirty = true; \
413 } while (0)
414
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415void SkPath::incReserve(U16CPU inc) {
416 SkDEBUGCODE(this->validate();)
417
418 fVerbs.setReserve(fVerbs.count() + inc);
419 fPts.setReserve(fPts.count() + inc);
420
421 SkDEBUGCODE(this->validate();)
422}
423
424void SkPath::moveTo(SkScalar x, SkScalar y) {
425 SkDEBUGCODE(this->validate();)
426
427 int vc = fVerbs.count();
428 SkPoint* pt;
429
reed@google.comd335d1d2012-01-12 18:17:11 +0000430 // remember our index
431 fLastMoveToIndex = fPts.count();
432
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000433 pt = fPts.append();
434 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435 pt->set(x, y);
436
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000437 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000438 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439}
440
441void SkPath::rMoveTo(SkScalar x, SkScalar y) {
442 SkPoint pt;
443 this->getLastPt(&pt);
444 this->moveTo(pt.fX + x, pt.fY + y);
445}
446
reed@google.comd335d1d2012-01-12 18:17:11 +0000447void SkPath::injectMoveToIfNeeded() {
448 if (fLastMoveToIndex < 0) {
449 SkScalar x, y;
450 if (fVerbs.count() == 0) {
451 x = y = 0;
452 } else {
453 const SkPoint& pt = fPts[~fLastMoveToIndex];
454 x = pt.fX;
455 y = pt.fY;
456 }
457 this->moveTo(x, y);
458 }
459}
460
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461void SkPath::lineTo(SkScalar x, SkScalar y) {
462 SkDEBUGCODE(this->validate();)
463
reed@google.comd335d1d2012-01-12 18:17:11 +0000464 this->injectMoveToIfNeeded();
465
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 fPts.append()->set(x, y);
467 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000468 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000470 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000471 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472}
473
474void SkPath::rLineTo(SkScalar x, SkScalar y) {
475 SkPoint pt;
476 this->getLastPt(&pt);
477 this->lineTo(pt.fX + x, pt.fY + y);
478}
479
480void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
481 SkDEBUGCODE(this->validate();)
482
reed@google.comd335d1d2012-01-12 18:17:11 +0000483 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484
485 SkPoint* pts = fPts.append(2);
486 pts[0].set(x1, y1);
487 pts[1].set(x2, y2);
488 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000489 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000491 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000492 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493}
494
495void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
496 SkPoint pt;
497 this->getLastPt(&pt);
498 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
499}
500
501void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
502 SkScalar x3, SkScalar y3) {
503 SkDEBUGCODE(this->validate();)
504
reed@google.comd335d1d2012-01-12 18:17:11 +0000505 this->injectMoveToIfNeeded();
506
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 SkPoint* pts = fPts.append(3);
508 pts[0].set(x1, y1);
509 pts[1].set(x2, y2);
510 pts[2].set(x3, y3);
511 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000512 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000513
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000514 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000515 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516}
517
518void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
519 SkScalar x3, SkScalar y3) {
520 SkPoint pt;
521 this->getLastPt(&pt);
522 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
523 pt.fX + x3, pt.fY + y3);
524}
525
526void SkPath::close() {
527 SkDEBUGCODE(this->validate();)
528
529 int count = fVerbs.count();
530 if (count > 0) {
531 switch (fVerbs[count - 1]) {
532 case kLine_Verb:
533 case kQuad_Verb:
534 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000535 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000537 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538 break;
539 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000540 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000541 break;
542 }
543 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000544
545 // signal that we need a moveTo to follow us (unless we're done)
546#if 0
547 if (fLastMoveToIndex >= 0) {
548 fLastMoveToIndex = ~fLastMoveToIndex;
549 }
550#else
551 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
552#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000553}
554
555///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000556
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557void SkPath::addRect(const SkRect& rect, Direction dir) {
558 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
559}
560
561void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
562 SkScalar bottom, Direction dir) {
563 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
564
565 this->incReserve(5);
566
567 this->moveTo(left, top);
568 if (dir == kCCW_Direction) {
569 this->lineTo(left, bottom);
570 this->lineTo(right, bottom);
571 this->lineTo(right, top);
572 } else {
573 this->lineTo(right, top);
574 this->lineTo(right, bottom);
575 this->lineTo(left, bottom);
576 }
577 this->close();
578}
579
580#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
581
582void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
583 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 SkScalar w = rect.width();
585 SkScalar halfW = SkScalarHalf(w);
586 SkScalar h = rect.height();
587 SkScalar halfH = SkScalarHalf(h);
588
589 if (halfW <= 0 || halfH <= 0) {
590 return;
591 }
592
reed@google.comabf15c12011-01-18 20:35:51 +0000593 bool skip_hori = rx >= halfW;
594 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000595
596 if (skip_hori && skip_vert) {
597 this->addOval(rect, dir);
598 return;
599 }
reed@google.comabf15c12011-01-18 20:35:51 +0000600
601 SkAutoPathBoundsUpdate apbu(this, rect);
602
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 if (skip_hori) {
604 rx = halfW;
605 } else if (skip_vert) {
606 ry = halfH;
607 }
608
609 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
610 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
611
612 this->incReserve(17);
613 this->moveTo(rect.fRight - rx, rect.fTop);
614 if (dir == kCCW_Direction) {
615 if (!skip_hori) {
616 this->lineTo(rect.fLeft + rx, rect.fTop); // top
617 }
618 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
619 rect.fLeft, rect.fTop + ry - sy,
620 rect.fLeft, rect.fTop + ry); // top-left
621 if (!skip_vert) {
622 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
623 }
624 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
625 rect.fLeft + rx - sx, rect.fBottom,
626 rect.fLeft + rx, rect.fBottom); // bot-left
627 if (!skip_hori) {
628 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
629 }
630 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
631 rect.fRight, rect.fBottom - ry + sy,
632 rect.fRight, rect.fBottom - ry); // bot-right
633 if (!skip_vert) {
634 this->lineTo(rect.fRight, rect.fTop + ry);
635 }
636 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
637 rect.fRight - rx + sx, rect.fTop,
638 rect.fRight - rx, rect.fTop); // top-right
639 } else {
640 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
641 rect.fRight, rect.fTop + ry - sy,
642 rect.fRight, rect.fTop + ry); // top-right
643 if (!skip_vert) {
644 this->lineTo(rect.fRight, rect.fBottom - ry);
645 }
646 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
647 rect.fRight - rx + sx, rect.fBottom,
648 rect.fRight - rx, rect.fBottom); // bot-right
649 if (!skip_hori) {
650 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
651 }
652 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
653 rect.fLeft, rect.fBottom - ry + sy,
654 rect.fLeft, rect.fBottom - ry); // bot-left
655 if (!skip_vert) {
656 this->lineTo(rect.fLeft, rect.fTop + ry); // left
657 }
658 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
659 rect.fLeft + rx - sx, rect.fTop,
660 rect.fLeft + rx, rect.fTop); // top-left
661 if (!skip_hori) {
662 this->lineTo(rect.fRight - rx, rect.fTop); // top
663 }
664 }
665 this->close();
666}
667
668static void add_corner_arc(SkPath* path, const SkRect& rect,
669 SkScalar rx, SkScalar ry, int startAngle,
670 SkPath::Direction dir, bool forceMoveTo) {
671 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
672 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674 SkRect r;
675 r.set(-rx, -ry, rx, ry);
676
677 switch (startAngle) {
678 case 0:
679 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
680 break;
681 case 90:
682 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
683 break;
684 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
685 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000686 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 }
reed@google.comabf15c12011-01-18 20:35:51 +0000688
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 SkScalar start = SkIntToScalar(startAngle);
690 SkScalar sweep = SkIntToScalar(90);
691 if (SkPath::kCCW_Direction == dir) {
692 start += sweep;
693 sweep = -sweep;
694 }
reed@google.comabf15c12011-01-18 20:35:51 +0000695
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 path->arcTo(r, start, sweep, forceMoveTo);
697}
698
699void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
700 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000701 // abort before we invoke SkAutoPathBoundsUpdate()
702 if (rect.isEmpty()) {
703 return;
704 }
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 SkAutoPathBoundsUpdate apbu(this, rect);
707
708 if (kCW_Direction == dir) {
709 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
710 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
711 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
712 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
713 } else {
714 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
715 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
716 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
717 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
718 }
719 this->close();
720}
721
722void SkPath::addOval(const SkRect& oval, Direction dir) {
723 SkAutoPathBoundsUpdate apbu(this, oval);
724
725 SkScalar cx = oval.centerX();
726 SkScalar cy = oval.centerY();
727 SkScalar rx = SkScalarHalf(oval.width());
728 SkScalar ry = SkScalarHalf(oval.height());
729#if 0 // these seem faster than using quads (1/2 the number of edges)
730 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
731 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
732
733 this->incReserve(13);
734 this->moveTo(cx + rx, cy);
735 if (dir == kCCW_Direction) {
736 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
737 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
738 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
739 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
740 } else {
741 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
742 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
743 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
744 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
745 }
746#else
747 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
748 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
749 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
750 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
751
752 /*
753 To handle imprecision in computing the center and radii, we revert to
754 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
755 to ensure that we don't exceed the oval's bounds *ever*, since we want
756 to use oval for our fast-bounds, rather than have to recompute it.
757 */
758 const SkScalar L = oval.fLeft; // cx - rx
759 const SkScalar T = oval.fTop; // cy - ry
760 const SkScalar R = oval.fRight; // cx + rx
761 const SkScalar B = oval.fBottom; // cy + ry
762
763 this->incReserve(17); // 8 quads + close
764 this->moveTo(R, cy);
765 if (dir == kCCW_Direction) {
766 this->quadTo( R, cy - sy, cx + mx, cy - my);
767 this->quadTo(cx + sx, T, cx , T);
768 this->quadTo(cx - sx, T, cx - mx, cy - my);
769 this->quadTo( L, cy - sy, L, cy );
770 this->quadTo( L, cy + sy, cx - mx, cy + my);
771 this->quadTo(cx - sx, B, cx , B);
772 this->quadTo(cx + sx, B, cx + mx, cy + my);
773 this->quadTo( R, cy + sy, R, cy );
774 } else {
775 this->quadTo( R, cy + sy, cx + mx, cy + my);
776 this->quadTo(cx + sx, B, cx , B);
777 this->quadTo(cx - sx, B, cx - mx, cy + my);
778 this->quadTo( L, cy + sy, L, cy );
779 this->quadTo( L, cy - sy, cx - mx, cy - my);
780 this->quadTo(cx - sx, T, cx , T);
781 this->quadTo(cx + sx, T, cx + mx, cy - my);
782 this->quadTo( R, cy - sy, R, cy );
783 }
784#endif
785 this->close();
786}
787
788void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
789 if (r > 0) {
790 SkRect rect;
791 rect.set(x - r, y - r, x + r, y + r);
792 this->addOval(rect, dir);
793 }
794}
795
796#include "SkGeometry.h"
797
798static int build_arc_points(const SkRect& oval, SkScalar startAngle,
799 SkScalar sweepAngle,
800 SkPoint pts[kSkBuildQuadArcStorage]) {
801 SkVector start, stop;
802
803 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
804 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
805 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000806
807 /* If the sweep angle is nearly (but less than) 360, then due to precision
808 loss in radians-conversion and/or sin/cos, we may end up with coincident
809 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
810 of drawing a nearly complete circle (good).
811 e.g. canvas.drawArc(0, 359.99, ...)
812 -vs- canvas.drawArc(0, 359.9, ...)
813 We try to detect this edge case, and tweak the stop vector
814 */
815 if (start == stop) {
816 SkScalar sw = SkScalarAbs(sweepAngle);
817 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
818 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
819 // make a guess at a tiny angle (in radians) to tweak by
820 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
821 // not sure how much will be enough, so we use a loop
822 do {
823 stopRad -= deltaRad;
824 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
825 } while (start == stop);
826 }
827 }
828
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000830
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
832 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000833
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 return SkBuildQuadArc(start, stop,
835 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
836 &matrix, pts);
837}
838
839void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
840 bool forceMoveTo) {
841 if (oval.width() < 0 || oval.height() < 0) {
842 return;
843 }
844
845 SkPoint pts[kSkBuildQuadArcStorage];
846 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
847 SkASSERT((count & 1) == 1);
848
849 if (fVerbs.count() == 0) {
850 forceMoveTo = true;
851 }
852 this->incReserve(count);
853 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
854 for (int i = 1; i < count; i += 2) {
855 this->quadTo(pts[i], pts[i+1]);
856 }
857}
858
859void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
860 SkScalar sweepAngle) {
861 if (oval.isEmpty() || 0 == sweepAngle) {
862 return;
863 }
864
865 const SkScalar kFullCircleAngle = SkIntToScalar(360);
866
867 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
868 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
869 return;
870 }
871
872 SkPoint pts[kSkBuildQuadArcStorage];
873 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
874
875 this->incReserve(count);
876 this->moveTo(pts[0]);
877 for (int i = 1; i < count; i += 2) {
878 this->quadTo(pts[i], pts[i+1]);
879 }
880}
881
882/*
883 Need to handle the case when the angle is sharp, and our computed end-points
884 for the arc go behind pt1 and/or p2...
885*/
886void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
887 SkScalar radius) {
888 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000889
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 // need to know our prev pt so we can construct tangent vectors
891 {
892 SkPoint start;
893 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000894 // Handle degenerate cases by adding a line to the first point and
895 // bailing out.
896 if ((x1 == start.fX && y1 == start.fY) ||
897 (x1 == x2 && y1 == y2) ||
898 radius == 0) {
899 this->lineTo(x1, y1);
900 return;
901 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902 before.setNormalize(x1 - start.fX, y1 - start.fY);
903 after.setNormalize(x2 - x1, y2 - y1);
904 }
reed@google.comabf15c12011-01-18 20:35:51 +0000905
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 SkScalar cosh = SkPoint::DotProduct(before, after);
907 SkScalar sinh = SkPoint::CrossProduct(before, after);
908
909 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000910 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 return;
912 }
reed@google.comabf15c12011-01-18 20:35:51 +0000913
reed@android.com8a1c16f2008-12-17 15:59:43 +0000914 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
915 if (dist < 0) {
916 dist = -dist;
917 }
918
919 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
920 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
921 SkRotationDirection arcDir;
922
923 // now turn before/after into normals
924 if (sinh > 0) {
925 before.rotateCCW();
926 after.rotateCCW();
927 arcDir = kCW_SkRotationDirection;
928 } else {
929 before.rotateCW();
930 after.rotateCW();
931 arcDir = kCCW_SkRotationDirection;
932 }
933
934 SkMatrix matrix;
935 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000936
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937 matrix.setScale(radius, radius);
938 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
939 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000940
reed@android.com8a1c16f2008-12-17 15:59:43 +0000941 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000942
reed@android.com8a1c16f2008-12-17 15:59:43 +0000943 this->incReserve(count);
944 // [xx,yy] == pts[0]
945 this->lineTo(xx, yy);
946 for (int i = 1; i < count; i += 2) {
947 this->quadTo(pts[i], pts[i+1]);
948 }
949}
950
951///////////////////////////////////////////////////////////////////////////////
952
953void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
954 SkMatrix matrix;
955
956 matrix.setTranslate(dx, dy);
957 this->addPath(path, matrix);
958}
959
960void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
961 this->incReserve(path.fPts.count());
962
schenney@chromium.org6630d8d2012-01-04 21:05:51 +0000963 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 SkPoint pts[4];
965 Verb verb;
966
967 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
968
969 while ((verb = iter.next(pts)) != kDone_Verb) {
970 switch (verb) {
971 case kMove_Verb:
972 proc(matrix, &pts[0], &pts[0], 1);
973 this->moveTo(pts[0]);
974 break;
975 case kLine_Verb:
976 proc(matrix, &pts[1], &pts[1], 1);
977 this->lineTo(pts[1]);
978 break;
979 case kQuad_Verb:
980 proc(matrix, &pts[1], &pts[1], 2);
981 this->quadTo(pts[1], pts[2]);
982 break;
983 case kCubic_Verb:
984 proc(matrix, &pts[1], &pts[1], 3);
985 this->cubicTo(pts[1], pts[2], pts[3]);
986 break;
987 case kClose_Verb:
988 this->close();
989 break;
990 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000991 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 }
993 }
994}
995
996///////////////////////////////////////////////////////////////////////////////
997
998static const uint8_t gPtsInVerb[] = {
999 1, // kMove
1000 1, // kLine
1001 2, // kQuad
1002 3, // kCubic
1003 0, // kClose
1004 0 // kDone
1005};
1006
1007// ignore the initial moveto, and stop when the 1st contour ends
1008void SkPath::pathTo(const SkPath& path) {
1009 int i, vcount = path.fVerbs.count();
1010 if (vcount == 0) {
1011 return;
1012 }
1013
1014 this->incReserve(vcount);
1015
1016 const uint8_t* verbs = path.fVerbs.begin();
1017 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1018
1019 SkASSERT(verbs[0] == kMove_Verb);
1020 for (i = 1; i < vcount; i++) {
1021 switch (verbs[i]) {
1022 case kLine_Verb:
1023 this->lineTo(pts[0].fX, pts[0].fY);
1024 break;
1025 case kQuad_Verb:
1026 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1027 break;
1028 case kCubic_Verb:
1029 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1030 pts[2].fX, pts[2].fY);
1031 break;
1032 case kClose_Verb:
1033 return;
1034 }
1035 pts += gPtsInVerb[verbs[i]];
1036 }
1037}
1038
1039// ignore the last point of the 1st contour
1040void SkPath::reversePathTo(const SkPath& path) {
1041 int i, vcount = path.fVerbs.count();
1042 if (vcount == 0) {
1043 return;
1044 }
1045
1046 this->incReserve(vcount);
1047
1048 const uint8_t* verbs = path.fVerbs.begin();
1049 const SkPoint* pts = path.fPts.begin();
1050
1051 SkASSERT(verbs[0] == kMove_Verb);
1052 for (i = 1; i < vcount; i++) {
1053 int n = gPtsInVerb[verbs[i]];
1054 if (n == 0) {
1055 break;
1056 }
1057 pts += n;
1058 }
1059
1060 while (--i > 0) {
1061 switch (verbs[i]) {
1062 case kLine_Verb:
1063 this->lineTo(pts[-1].fX, pts[-1].fY);
1064 break;
1065 case kQuad_Verb:
1066 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1067 break;
1068 case kCubic_Verb:
1069 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1070 pts[-3].fX, pts[-3].fY);
1071 break;
1072 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001073 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 break;
1075 }
1076 pts -= gPtsInVerb[verbs[i]];
1077 }
1078}
1079
reed@google.com63d73742012-01-10 15:33:12 +00001080void SkPath::reverseAddPath(const SkPath& src) {
1081 this->incReserve(src.fPts.count());
1082
1083 const SkPoint* startPts = src.fPts.begin();
1084 const SkPoint* pts = src.fPts.end();
1085 const uint8_t* startVerbs = src.fVerbs.begin();
1086 const uint8_t* verbs = src.fVerbs.end();
1087
1088 bool needMove = true;
1089 bool needClose = false;
1090 while (verbs > startVerbs) {
1091 uint8_t v = *--verbs;
1092 int n = gPtsInVerb[v];
1093
1094 if (needMove) {
1095 --pts;
1096 this->moveTo(pts->fX, pts->fY);
1097 needMove = false;
1098 }
1099 pts -= n;
1100 switch (v) {
1101 case kMove_Verb:
1102 if (needClose) {
1103 this->close();
1104 needClose = false;
1105 }
1106 needMove = true;
1107 pts += 1; // so we see the point in "if (needMove)" above
1108 break;
1109 case kLine_Verb:
1110 this->lineTo(pts[0]);
1111 break;
1112 case kQuad_Verb:
1113 this->quadTo(pts[1], pts[0]);
1114 break;
1115 case kCubic_Verb:
1116 this->cubicTo(pts[2], pts[1], pts[0]);
1117 break;
1118 case kClose_Verb:
1119 needClose = true;
1120 break;
1121 default:
1122 SkASSERT(!"unexpected verb");
1123 }
1124 }
1125}
1126
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127///////////////////////////////////////////////////////////////////////////////
1128
1129void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1130 SkMatrix matrix;
1131
1132 matrix.setTranslate(dx, dy);
1133 this->transform(matrix, dst);
1134}
1135
1136#include "SkGeometry.h"
1137
1138static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1139 int level = 2) {
1140 if (--level >= 0) {
1141 SkPoint tmp[5];
1142
1143 SkChopQuadAtHalf(pts, tmp);
1144 subdivide_quad_to(path, &tmp[0], level);
1145 subdivide_quad_to(path, &tmp[2], level);
1146 } else {
1147 path->quadTo(pts[1], pts[2]);
1148 }
1149}
1150
1151static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1152 int level = 2) {
1153 if (--level >= 0) {
1154 SkPoint tmp[7];
1155
1156 SkChopCubicAtHalf(pts, tmp);
1157 subdivide_cubic_to(path, &tmp[0], level);
1158 subdivide_cubic_to(path, &tmp[3], level);
1159 } else {
1160 path->cubicTo(pts[1], pts[2], pts[3]);
1161 }
1162}
1163
1164void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1165 SkDEBUGCODE(this->validate();)
1166 if (dst == NULL) {
1167 dst = (SkPath*)this;
1168 }
1169
tomhudson@google.com8d430182011-06-06 19:11:19 +00001170 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 SkPath tmp;
1172 tmp.fFillType = fFillType;
1173
1174 SkPath::Iter iter(*this, false);
1175 SkPoint pts[4];
1176 SkPath::Verb verb;
1177
1178 while ((verb = iter.next(pts)) != kDone_Verb) {
1179 switch (verb) {
1180 case kMove_Verb:
1181 tmp.moveTo(pts[0]);
1182 break;
1183 case kLine_Verb:
1184 tmp.lineTo(pts[1]);
1185 break;
1186 case kQuad_Verb:
1187 subdivide_quad_to(&tmp, pts);
1188 break;
1189 case kCubic_Verb:
1190 subdivide_cubic_to(&tmp, pts);
1191 break;
1192 case kClose_Verb:
1193 tmp.close();
1194 break;
1195 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001196 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 break;
1198 }
1199 }
1200
1201 dst->swap(tmp);
1202 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1203 } else {
1204 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001205 // fBoundsIsDirty before we set it
1206 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001208 matrix.mapRect(&dst->fBounds, fBounds);
1209 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001211 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001212 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 }
1214
1215 if (this != dst) {
1216 dst->fVerbs = fVerbs;
1217 dst->fPts.setCount(fPts.count());
1218 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001219 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001220 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 }
1222 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1223 SkDEBUGCODE(dst->validate();)
1224 }
1225}
1226
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227///////////////////////////////////////////////////////////////////////////////
1228///////////////////////////////////////////////////////////////////////////////
1229
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001230enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001231 kEmptyContour_SegmentState, // The current contour is empty. We may be
1232 // starting processing or we may have just
1233 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001234 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1235 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1236 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237};
1238
1239SkPath::Iter::Iter() {
1240#ifdef SK_DEBUG
1241 fPts = NULL;
1242 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001243 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001244 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245#endif
1246 // need to init enough to make next() harmlessly return kDone_Verb
1247 fVerbs = NULL;
1248 fVerbStop = NULL;
1249 fNeedClose = false;
1250}
1251
1252SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1253 this->setPath(path, forceClose);
1254}
1255
1256void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1257 fPts = path.fPts.begin();
1258 fVerbs = path.fVerbs.begin();
1259 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001260 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001261 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 fForceClose = SkToU8(forceClose);
1263 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001264 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265}
1266
1267bool SkPath::Iter::isClosedContour() const {
1268 if (fVerbs == NULL || fVerbs == fVerbStop) {
1269 return false;
1270 }
1271 if (fForceClose) {
1272 return true;
1273 }
1274
1275 const uint8_t* verbs = fVerbs;
1276 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 if (kMove_Verb == *verbs) {
1279 verbs += 1; // skip the initial moveto
1280 }
1281
1282 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001283 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 if (kMove_Verb == v) {
1285 break;
1286 }
1287 if (kClose_Verb == v) {
1288 return true;
1289 }
1290 }
1291 return false;
1292}
1293
1294SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1295 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001296 // A special case: if both points are NaN, SkPoint::operation== returns
1297 // false, but the iterator expects that they are treated as the same.
1298 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001299 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1300 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001301 return kClose_Verb;
1302 }
1303
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 if (pts) {
1305 pts[0] = fLastPt;
1306 pts[1] = fMoveTo;
1307 }
1308 fLastPt = fMoveTo;
1309 fCloseLine = true;
1310 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001311 } else {
1312 pts[0] = fMoveTo;
1313 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315}
1316
1317bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001318 if (fSegmentState == kAfterMove_SegmentState) {
1319 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 if (pts) {
1321 *pts = fMoveTo;
1322 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001323 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001325 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1326 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 if (pts) {
1328 *pts = fPts[-1];
1329 }
1330 }
1331 return false;
1332}
1333
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001334void SkPath::Iter::consumeDegenerateSegments() {
1335 // We need to step over anything that will not move the current draw point
1336 // forward before the next move is seen
1337 const uint8_t* lastMoveVerb = 0;
1338 const SkPoint* lastMovePt = 0;
1339 SkPoint lastPt = fLastPt;
1340 while (fVerbs != fVerbStop) {
1341 unsigned verb = *fVerbs;
1342 switch (verb) {
1343 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001344 // Keep a record of this most recent move
1345 lastMoveVerb = fVerbs;
1346 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001347 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001348 fVerbs++;
1349 fPts++;
1350 break;
1351
1352 case kClose_Verb:
1353 // A close when we are in a segment is always valid
1354 if (fSegmentState == kAfterPrimitive_SegmentState) {
1355 return;
1356 }
1357 // A close at any other time must be ignored
1358 fVerbs++;
1359 break;
1360
1361 case kLine_Verb:
1362 if (!IsLineDegenerate(lastPt, fPts[0])) {
1363 if (lastMoveVerb) {
1364 fVerbs = lastMoveVerb;
1365 fPts = lastMovePt;
1366 return;
1367 }
1368 return;
1369 }
1370 // Ignore this line and continue
1371 fVerbs++;
1372 fPts++;
1373 break;
1374
1375 case kQuad_Verb:
1376 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1377 if (lastMoveVerb) {
1378 fVerbs = lastMoveVerb;
1379 fPts = lastMovePt;
1380 return;
1381 }
1382 return;
1383 }
1384 // Ignore this line and continue
1385 fVerbs++;
1386 fPts += 2;
1387 break;
1388
1389 case kCubic_Verb:
1390 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1391 if (lastMoveVerb) {
1392 fVerbs = lastMoveVerb;
1393 fPts = lastMovePt;
1394 return;
1395 }
1396 return;
1397 }
1398 // Ignore this line and continue
1399 fVerbs++;
1400 fPts += 3;
1401 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001402
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001403 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001404 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001405 }
1406 }
1407}
1408
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001410 this->consumeDegenerateSegments();
1411
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001413 // Close the curve if requested and if there is some curve to close
1414 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415 if (kLine_Verb == this->autoClose(pts)) {
1416 return kLine_Verb;
1417 }
1418 fNeedClose = false;
1419 return kClose_Verb;
1420 }
1421 return kDone_Verb;
1422 }
1423
1424 unsigned verb = *fVerbs++;
1425 const SkPoint* srcPts = fPts;
1426
1427 switch (verb) {
1428 case kMove_Verb:
1429 if (fNeedClose) {
1430 fVerbs -= 1;
1431 verb = this->autoClose(pts);
1432 if (verb == kClose_Verb) {
1433 fNeedClose = false;
1434 }
1435 return (Verb)verb;
1436 }
1437 if (fVerbs == fVerbStop) { // might be a trailing moveto
1438 return kDone_Verb;
1439 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001440 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 if (pts) {
1442 pts[0] = *srcPts;
1443 }
1444 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001445 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001446 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 fNeedClose = fForceClose;
1448 break;
1449 case kLine_Verb:
1450 if (this->cons_moveTo(pts)) {
1451 return kMove_Verb;
1452 }
1453 if (pts) {
1454 pts[1] = srcPts[0];
1455 }
1456 fLastPt = srcPts[0];
1457 fCloseLine = false;
1458 srcPts += 1;
1459 break;
1460 case kQuad_Verb:
1461 if (this->cons_moveTo(pts)) {
1462 return kMove_Verb;
1463 }
1464 if (pts) {
1465 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1466 }
1467 fLastPt = srcPts[1];
1468 srcPts += 2;
1469 break;
1470 case kCubic_Verb:
1471 if (this->cons_moveTo(pts)) {
1472 return kMove_Verb;
1473 }
1474 if (pts) {
1475 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1476 }
1477 fLastPt = srcPts[2];
1478 srcPts += 3;
1479 break;
1480 case kClose_Verb:
1481 verb = this->autoClose(pts);
1482 if (verb == kLine_Verb) {
1483 fVerbs -= 1;
1484 } else {
1485 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001486 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001488 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 break;
1490 }
1491 fPts = srcPts;
1492 return (Verb)verb;
1493}
1494
1495///////////////////////////////////////////////////////////////////////////////
1496
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001497SkPath::RawIter::RawIter() {
1498#ifdef SK_DEBUG
1499 fPts = NULL;
1500 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1501#endif
1502 // need to init enough to make next() harmlessly return kDone_Verb
1503 fVerbs = NULL;
1504 fVerbStop = NULL;
1505}
1506
1507SkPath::RawIter::RawIter(const SkPath& path) {
1508 this->setPath(path);
1509}
1510
1511void SkPath::RawIter::setPath(const SkPath& path) {
1512 fPts = path.fPts.begin();
1513 fVerbs = path.fVerbs.begin();
1514 fVerbStop = path.fVerbs.end();
1515 fMoveTo.fX = fMoveTo.fY = 0;
1516 fLastPt.fX = fLastPt.fY = 0;
1517}
1518
1519SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1520 if (fVerbs == fVerbStop) {
1521 return kDone_Verb;
1522 }
1523
1524 unsigned verb = *fVerbs++;
1525 const SkPoint* srcPts = fPts;
1526
1527 switch (verb) {
1528 case kMove_Verb:
1529 if (pts) {
1530 pts[0] = *srcPts;
1531 }
1532 fMoveTo = srcPts[0];
1533 fLastPt = fMoveTo;
1534 srcPts += 1;
1535 break;
1536 case kLine_Verb:
1537 if (pts) {
1538 pts[0] = fLastPt;
1539 pts[1] = srcPts[0];
1540 }
1541 fLastPt = srcPts[0];
1542 srcPts += 1;
1543 break;
1544 case kQuad_Verb:
1545 if (pts) {
1546 pts[0] = fLastPt;
1547 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1548 }
1549 fLastPt = srcPts[1];
1550 srcPts += 2;
1551 break;
1552 case kCubic_Verb:
1553 if (pts) {
1554 pts[0] = fLastPt;
1555 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1556 }
1557 fLastPt = srcPts[2];
1558 srcPts += 3;
1559 break;
1560 case kClose_Verb:
1561 fLastPt = fMoveTo;
1562 if (pts) {
1563 pts[0] = fMoveTo;
1564 }
1565 break;
1566 }
1567 fPts = srcPts;
1568 return (Verb)verb;
1569}
1570
1571///////////////////////////////////////////////////////////////////////////////
1572
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573/*
1574 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1575*/
1576
reed@google.com73945652011-04-25 19:04:27 +00001577void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 SkDEBUGCODE(this->validate();)
1579
1580 buffer.write32(fPts.count());
1581 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001582 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1584 buffer.writePad(fVerbs.begin(), fVerbs.count());
1585}
1586
reed@google.com73945652011-04-25 19:04:27 +00001587void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 fPts.setCount(buffer.readS32());
1589 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001590 uint32_t packed = buffer.readS32();
1591 fFillType = packed >> 8;
1592 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1594 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001595
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001596 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001597 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598
1599 SkDEBUGCODE(this->validate();)
1600}
1601
1602///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604void SkPath::dump(bool forceClose, const char title[]) const {
1605 Iter iter(*this, forceClose);
1606 SkPoint pts[4];
1607 Verb verb;
1608
1609 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1610 title ? title : "");
1611
1612 while ((verb = iter.next(pts)) != kDone_Verb) {
1613 switch (verb) {
1614 case kMove_Verb:
1615#ifdef SK_CAN_USE_FLOAT
1616 SkDebugf(" path: moveTo [%g %g]\n",
1617 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1618#else
1619 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1620#endif
1621 break;
1622 case kLine_Verb:
1623#ifdef SK_CAN_USE_FLOAT
1624 SkDebugf(" path: lineTo [%g %g]\n",
1625 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1626#else
1627 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1628#endif
1629 break;
1630 case kQuad_Verb:
1631#ifdef SK_CAN_USE_FLOAT
1632 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1633 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1634 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1635#else
1636 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1637 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1638#endif
1639 break;
1640 case kCubic_Verb:
1641#ifdef SK_CAN_USE_FLOAT
1642 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1643 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1644 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1645 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1646#else
1647 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1648 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1649 pts[3].fX, pts[3].fY);
1650#endif
1651 break;
1652 case kClose_Verb:
1653 SkDebugf(" path: close\n");
1654 break;
1655 default:
1656 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1657 verb = kDone_Verb; // stop the loop
1658 break;
1659 }
1660 }
1661 SkDebugf("path: done %s\n", title ? title : "");
1662}
1663
reed@android.come522ca52009-11-23 20:10:41 +00001664void SkPath::dump() const {
1665 this->dump(false);
1666}
1667
1668#ifdef SK_DEBUG
1669void SkPath::validate() const {
1670 SkASSERT(this != NULL);
1671 SkASSERT((fFillType & ~3) == 0);
1672 fPts.validate();
1673 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001674
reed@android.come522ca52009-11-23 20:10:41 +00001675 if (!fBoundsIsDirty) {
1676 SkRect bounds;
1677 compute_pt_bounds(&bounds, fPts);
1678 if (fPts.count() <= 1) {
1679 // if we're empty, fBounds may be empty but translated, so we can't
1680 // necessarily compare to bounds directly
1681 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1682 // be [2, 2, 2, 2]
1683 SkASSERT(bounds.isEmpty());
1684 SkASSERT(fBounds.isEmpty());
1685 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001686 if (bounds.isEmpty()) {
1687 SkASSERT(fBounds.isEmpty());
1688 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001689 if (!fBounds.isEmpty()) {
1690 SkASSERT(fBounds.contains(bounds));
1691 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001692 }
reed@android.come522ca52009-11-23 20:10:41 +00001693 }
1694 }
reed@google.com10296cc2011-09-21 12:29:05 +00001695
1696 uint32_t mask = 0;
1697 for (int i = 0; i < fVerbs.count(); i++) {
1698 switch (fVerbs[i]) {
1699 case kLine_Verb:
1700 mask |= kLine_SegmentMask;
1701 break;
1702 case kQuad_Verb:
1703 mask |= kQuad_SegmentMask;
1704 break;
1705 case kCubic_Verb:
1706 mask |= kCubic_SegmentMask;
1707 }
1708 }
1709 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001710}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711#endif
reed@android.come522ca52009-11-23 20:10:41 +00001712
reed@google.com04863fa2011-05-15 04:08:24 +00001713///////////////////////////////////////////////////////////////////////////////
1714
reed@google.com0b7b9822011-05-16 12:29:27 +00001715static int sign(SkScalar x) { return x < 0; }
1716#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001717
reed@google.com04863fa2011-05-15 04:08:24 +00001718static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001719 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001720}
1721
1722// only valid for a single contour
1723struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001724 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001725 fSign = 0;
1726 // warnings
1727 fCurrPt.set(0, 0);
1728 fVec0.set(0, 0);
1729 fVec1.set(0, 0);
1730 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001731
1732 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001733 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001734 }
1735
1736 SkPath::Convexity getConvexity() const { return fConvexity; }
1737
1738 void addPt(const SkPoint& pt) {
1739 if (SkPath::kConcave_Convexity == fConvexity) {
1740 return;
1741 }
1742
1743 if (0 == fPtCount) {
1744 fCurrPt = pt;
1745 ++fPtCount;
1746 } else {
1747 SkVector vec = pt - fCurrPt;
1748 if (vec.fX || vec.fY) {
1749 fCurrPt = pt;
1750 if (++fPtCount == 2) {
1751 fFirstVec = fVec1 = vec;
1752 } else {
1753 SkASSERT(fPtCount > 2);
1754 this->addVec(vec);
1755 }
reed@google.com85b6e392011-05-15 20:25:17 +00001756
1757 int sx = sign(vec.fX);
1758 int sy = sign(vec.fY);
1759 fDx += (sx != fSx);
1760 fDy += (sy != fSy);
1761 fSx = sx;
1762 fSy = sy;
1763
1764 if (fDx > 3 || fDy > 3) {
1765 fConvexity = SkPath::kConcave_Convexity;
1766 }
reed@google.com04863fa2011-05-15 04:08:24 +00001767 }
1768 }
1769 }
1770
1771 void close() {
1772 if (fPtCount > 2) {
1773 this->addVec(fFirstVec);
1774 }
1775 }
1776
1777private:
1778 void addVec(const SkVector& vec) {
1779 SkASSERT(vec.fX || vec.fY);
1780 fVec0 = fVec1;
1781 fVec1 = vec;
1782 int sign = CrossProductSign(fVec0, fVec1);
1783 if (0 == fSign) {
1784 fSign = sign;
1785 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001786 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001787 fConvexity = SkPath::kConcave_Convexity;
1788 }
1789 }
1790 }
1791
1792 SkPoint fCurrPt;
1793 SkVector fVec0, fVec1, fFirstVec;
1794 int fPtCount; // non-degenerate points
1795 int fSign;
1796 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001797 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001798};
1799
1800SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1801 SkPoint pts[4];
1802 SkPath::Verb verb;
1803 SkPath::Iter iter(path, true);
1804
1805 int contourCount = 0;
1806 int count;
1807 Convexicator state;
1808
1809 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1810 switch (verb) {
1811 case kMove_Verb:
1812 if (++contourCount > 1) {
1813 return kConcave_Convexity;
1814 }
1815 pts[1] = pts[0];
1816 count = 1;
1817 break;
1818 case kLine_Verb: count = 1; break;
1819 case kQuad_Verb: count = 2; break;
1820 case kCubic_Verb: count = 3; break;
1821 case kClose_Verb:
1822 state.close();
1823 count = 0;
1824 break;
1825 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001826 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001827 return kConcave_Convexity;
1828 }
1829
1830 for (int i = 1; i <= count; i++) {
1831 state.addPt(pts[i]);
1832 }
1833 // early exit
1834 if (kConcave_Convexity == state.getConvexity()) {
1835 return kConcave_Convexity;
1836 }
1837 }
1838 return state.getConvexity();
1839}
reed@google.com69a99432012-01-10 18:00:10 +00001840
1841///////////////////////////////////////////////////////////////////////////////
1842
1843class ContourIter {
1844public:
1845 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1846
1847 bool done() const { return fDone; }
1848 // if !done() then these may be called
1849 int count() const { return fCurrPtCount; }
1850 const SkPoint* pts() const { return fCurrPt; }
1851 void next();
1852
1853private:
1854 int fCurrPtCount;
1855 const SkPoint* fCurrPt;
1856 const uint8_t* fCurrVerb;
1857 const uint8_t* fStopVerbs;
1858 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001859 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001860};
1861
1862ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1863 const SkTDArray<SkPoint>& pts) {
1864 fStopVerbs = verbs.begin() + verbs.count();
1865
1866 fDone = false;
1867 fCurrPt = pts.begin();
1868 fCurrVerb = verbs.begin();
1869 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001870 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001871 this->next();
1872}
1873
1874void ContourIter::next() {
1875 if (fCurrVerb >= fStopVerbs) {
1876 fDone = true;
1877 }
1878 if (fDone) {
1879 return;
1880 }
1881
1882 // skip pts of prev contour
1883 fCurrPt += fCurrPtCount;
1884
1885 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1886 int ptCount = 1; // moveTo
1887 const uint8_t* verbs = fCurrVerb;
1888
1889 for (++verbs; verbs < fStopVerbs; ++verbs) {
1890 switch (*verbs) {
1891 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001892 goto CONTOUR_END;
1893 case SkPath::kLine_Verb:
1894 ptCount += 1;
1895 break;
1896 case SkPath::kQuad_Verb:
1897 ptCount += 2;
1898 break;
1899 case SkPath::kCubic_Verb:
1900 ptCount += 3;
1901 break;
1902 default: // kClose_Verb, just keep going
1903 break;
1904 }
1905 }
1906CONTOUR_END:
1907 fCurrPtCount = ptCount;
1908 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001909 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001910}
1911
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001912// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00001913static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001914 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
1915 // We may get 0 when the above subtracts underflow. We expect this to be
1916 // very rare and lazily promote to double.
1917 if (0 == cross) {
1918 double p0x = SkScalarToDouble(p0.fX);
1919 double p0y = SkScalarToDouble(p0.fY);
1920
1921 double p1x = SkScalarToDouble(p1.fX);
1922 double p1y = SkScalarToDouble(p1.fY);
1923
1924 double p2x = SkScalarToDouble(p2.fX);
1925 double p2y = SkScalarToDouble(p2.fY);
1926
1927 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
1928 (p1y - p0y) * (p2x - p0x));
1929
1930 }
1931 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00001932}
1933
reed@google.comc1ea60a2012-01-31 15:15:36 +00001934// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00001935static int find_max_y(const SkPoint pts[], int count) {
1936 SkASSERT(count > 0);
1937 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00001938 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00001939 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00001940 SkScalar y = pts[i].fY;
1941 if (y > max) {
1942 max = y;
1943 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00001944 }
1945 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00001946 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00001947}
1948
reed@google.comcabaf1d2012-01-11 21:03:05 +00001949static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
1950 int i = index;
1951 for (;;) {
1952 i = (i + inc) % n;
1953 if (i == index) { // we wrapped around, so abort
1954 break;
1955 }
1956 if (pts[index] != pts[i]) { // found a different point, success!
1957 break;
1958 }
1959 }
1960 return i;
1961}
1962
reed@google.comc1ea60a2012-01-31 15:15:36 +00001963/**
1964 * Starting at index, and moving forward (incrementing), find the xmin and
1965 * xmax of the contiguous points that have the same Y.
1966 */
1967static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
1968 int* maxIndexPtr) {
1969 const SkScalar y = pts[index].fY;
1970 SkScalar min = pts[index].fX;
1971 SkScalar max = min;
1972 int minIndex = index;
1973 int maxIndex = index;
1974 for (int i = index + 1; i < n; ++i) {
1975 if (pts[i].fY != y) {
1976 break;
1977 }
1978 SkScalar x = pts[i].fX;
1979 if (x < min) {
1980 min = x;
1981 minIndex = i;
1982 } else if (x > max) {
1983 max = x;
1984 maxIndex = i;
1985 }
1986 }
1987 *maxIndexPtr = maxIndex;
1988 return minIndex;
1989}
1990
reed@google.comac8543f2012-01-30 20:51:25 +00001991static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
1992 if (dir) {
1993 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
1994 }
1995 return true;
1996}
1997
reed@google.comc1ea60a2012-01-31 15:15:36 +00001998#if 0
1999#include "SkString.h"
2000#include "../utils/SkParsePath.h"
2001static void dumpPath(const SkPath& path) {
2002 SkString str;
2003 SkParsePath::ToSVGString(path, &str);
2004 SkDebugf("%s\n", str.c_str());
2005}
2006#endif
2007
reed@google.comac8543f2012-01-30 20:51:25 +00002008/*
2009 * We loop through all contours, and keep the computed cross-product of the
2010 * contour that contained the global y-max. If we just look at the first
2011 * contour, we may find one that is wound the opposite way (correctly) since
2012 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2013 * that is outer most (or at least has the global y-max) before we can consider
2014 * its cross product.
2015 */
reed@google.com69a99432012-01-10 18:00:10 +00002016bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002017// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002018 // don't want to pay the cost for computing this if it
2019 // is unknown, so we don't call isConvex()
2020 const Convexity conv = this->getConvexityOrUnknown();
2021
2022 ContourIter iter(fVerbs, fPts);
2023
reed@google.comac8543f2012-01-30 20:51:25 +00002024 // initialize with our logical y-min
2025 SkScalar ymax = this->getBounds().fTop;
2026 SkScalar ymaxCross = 0;
2027
reed@google.com69a99432012-01-10 18:00:10 +00002028 for (; !iter.done(); iter.next()) {
2029 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002030 if (n < 3) {
2031 continue;
2032 }
reed@google.comac8543f2012-01-30 20:51:25 +00002033
reed@google.comcabaf1d2012-01-11 21:03:05 +00002034 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002035 SkScalar cross = 0;
2036 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002037 // we loop, skipping over degenerate or flat segments that will
2038 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002039 for (int i = 0; i < n - 2; ++i) {
2040 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2041 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002042 // early-exit, as kConvex is assumed to have only 1
2043 // non-degenerate contour
2044 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002045 }
2046 }
2047 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002048 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002049 if (pts[index].fY < ymax) {
2050 continue;
2051 }
2052
reed@google.comc1ea60a2012-01-31 15:15:36 +00002053 // If there is more than 1 distinct point at the y-max, we take the
2054 // x-min and x-max of them and just subtract to compute the dir.
2055 if (pts[(index + 1) % n].fY == pts[index].fY) {
2056 int maxIndex;
2057 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002058 if (minIndex == maxIndex) {
2059 goto TRY_CROSSPROD;
2060 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002061 SkASSERT(pts[minIndex].fY == pts[index].fY);
2062 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2063 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2064 // we just subtract the indices, and let that auto-convert to
2065 // SkScalar, since we just want - or + to signal the direction.
2066 cross = minIndex - maxIndex;
2067 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002068 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002069 // Find a next and prev index to use for the cross-product test,
2070 // but we try to find pts that form non-zero vectors from pts[index]
2071 //
2072 // Its possible that we can't find two non-degenerate vectors, so
2073 // we have to guard our search (e.g. all the pts could be in the
2074 // same place).
2075
2076 // we pass n - 1 instead of -1 so we don't foul up % operator by
2077 // passing it a negative LH argument.
2078 int prev = find_diff_pt(pts, index, n, n - 1);
2079 if (prev == index) {
2080 // completely degenerate, skip to next contour
2081 continue;
2082 }
2083 int next = find_diff_pt(pts, index, n, 1);
2084 SkASSERT(next != index);
2085 cross = cross_prod(pts[prev], pts[index], pts[next]);
2086 // if we get a zero, but the pts aren't on top of each other, then
2087 // we can just look at the direction
2088 if (0 == cross) {
2089 // construct the subtract so we get the correct Direction below
2090 cross = pts[index].fX - pts[next].fX;
2091 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002092 }
reed@google.comac8543f2012-01-30 20:51:25 +00002093
2094 if (cross) {
2095 // record our best guess so far
2096 ymax = pts[index].fY;
2097 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002098 }
reed@google.com69a99432012-01-10 18:00:10 +00002099 }
2100 }
reed@google.com69a99432012-01-10 18:00:10 +00002101
reed@google.comac8543f2012-01-30 20:51:25 +00002102 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2103}