blob: ee6a1712f51fa22d9cda6af3653354b7e6dc06d9 [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
bsalomon@google.com6aa29652012-04-18 13:29:52 +000035class SkAutoDisableOvalCheck {
36public:
37 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
38 fSaved = fPath->fIsOval;
39 }
40
41 ~SkAutoDisableOvalCheck() {
42 fPath->fIsOval = fSaved;
43 }
44
45private:
46 SkPath* fPath;
47 bool fSaved;
48};
49
reed@android.com8a1c16f2008-12-17 15:59:43 +000050/* This guy's constructor/destructor bracket a path editing operation. It is
51 used when we know the bounds of the amount we are going to add to the path
52 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 It captures some state about the path up front (i.e. if it already has a
55 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000056 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000057
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000058 It also notes if the path was originally degenerate, and if so, sets
59 isConvex to true. Thus it can only be used if the contour being added is
60 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 */
62class SkAutoPathBoundsUpdate {
63public:
64 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
65 this->init(path);
66 }
67
68 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
69 SkScalar right, SkScalar bottom) {
70 fRect.set(left, top, right, bottom);
71 this->init(path);
72 }
reed@google.comabf15c12011-01-18 20:35:51 +000073
reed@android.com8a1c16f2008-12-17 15:59:43 +000074 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000075 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000077 fPath->fBounds = fRect;
78 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000080 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000081 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
88 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000095 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000096 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000098 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000099 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 }
101};
102
reed@android.comd252db02009-04-01 18:31:44 +0000103static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
105 bounds->set(0, 0, 0, 0);
106 } else {
107 bounds->set(pts.begin(), pts.count());
108// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
109 }
110}
111
112////////////////////////////////////////////////////////////////////////////
113
114/*
115 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
118
119 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000120 1. If we encounter degenerate segments, remove them
121 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
122 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
123 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124*/
125
126////////////////////////////////////////////////////////////////////////////
127
reed@google.comd335d1d2012-01-12 18:17:11 +0000128// flag to require a moveTo if we begin with something else, like lineTo etc.
129#define INITIAL_LASTMOVETOINDEX_VALUE ~0
130
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000131SkPath::SkPath()
132 : fFillType(kWinding_FillType)
133 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000134 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000135 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000136 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000137 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000138#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000139 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000140 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000141#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
144SkPath::SkPath(const SkPath& src) {
145 SkDEBUGCODE(src.validate();)
146 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000147#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000148 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000149 fGenerationID = src.fGenerationID;
150 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000151#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152}
153
154SkPath::~SkPath() {
155 SkDEBUGCODE(this->validate();)
156}
157
158SkPath& SkPath::operator=(const SkPath& src) {
159 SkDEBUGCODE(src.validate();)
160
161 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000162 fBounds = src.fBounds;
163 fPts = src.fPts;
164 fVerbs = src.fVerbs;
165 fFillType = src.fFillType;
166 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000167 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000168 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000169 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000170 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000171 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 }
173 SkDEBUGCODE(this->validate();)
174 return *this;
175}
176
reed@android.com3abec1d2009-03-02 05:36:20 +0000177bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000178 // note: don't need to look at isConvex or bounds, since just comparing the
179 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000180
181 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
182 // since it is only a cache of info in the fVerbs, but its a fast way to
183 // notice a difference
184
reed@android.com3abec1d2009-03-02 05:36:20 +0000185 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000186 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
187 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190void SkPath::swap(SkPath& other) {
191 SkASSERT(&other != NULL);
192
193 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000194 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 fPts.swap(other.fPts);
196 fVerbs.swap(other.fVerbs);
197 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000198 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000199 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000200 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000201 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000202 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000203 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 }
205}
206
djsollen@google.com56c69772011-11-08 19:00:26 +0000207#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000208uint32_t SkPath::getGenerationID() const {
209 return fGenerationID;
210}
djsollen@google.come63793a2012-03-21 15:39:03 +0000211
212const SkPath* SkPath::getSourcePath() const {
213 return fSourcePath;
214}
215
216void SkPath::setSourcePath(const SkPath* path) {
217 fSourcePath = path;
218}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000219#endif
220
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221void SkPath::reset() {
222 SkDEBUGCODE(this->validate();)
223
224 fPts.reset();
225 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000226 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000227 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000228 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000229 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000230 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000231 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232}
233
234void SkPath::rewind() {
235 SkDEBUGCODE(this->validate();)
236
237 fPts.rewind();
238 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000239 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000240 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000241 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000242 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000243 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000244 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245}
246
247bool SkPath::isEmpty() const {
248 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000249 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250}
251
reed@google.com7e6c4d12012-05-10 14:05:43 +0000252bool SkPath::isLine(SkPoint line[2]) const {
253 int verbCount = fVerbs.count();
254 int ptCount = fPts.count();
255
256 if (2 == verbCount && 2 == ptCount) {
257 const uint8_t* verbs = fVerbs.begin();
258 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
259 if (line) {
260 const SkPoint* pts = fPts.begin();
261 line[0] = pts[0];
262 line[1] = pts[1];
263 }
264 return true;
265 }
266 }
267 return false;
268}
269
caryclark@google.comf1316942011-07-26 19:54:45 +0000270/*
271 Determines if path is a rect by keeping track of changes in direction
272 and looking for a loop either clockwise or counterclockwise.
273
274 The direction is computed such that:
275 0: vertical up
276 1: horizontal right
277 2: vertical down
278 3: horizontal left
279
280A rectangle cycles up/right/down/left or up/left/down/right.
281
282The test fails if:
283 The path is closed, and followed by a line.
284 A second move creates a new endpoint.
285 A diagonal line is parsed.
286 There's more than four changes of direction.
287 There's a discontinuity on the line (e.g., a move in the middle)
288 The line reverses direction.
289 The rectangle doesn't complete a cycle.
290 The path contains a quadratic or cubic.
291 The path contains fewer than four points.
292 The final point isn't equal to the first point.
293
294It's OK if the path has:
295 Several colinear line segments composing a rectangle side.
296 Single points on the rectangle side.
297
298The direction takes advantage of the corners found since opposite sides
299must travel in opposite directions.
300
301FIXME: Allow colinear quads and cubics to be treated like lines.
302FIXME: If the API passes fill-only, return true if the filled stroke
303 is a rectangle, though the caller failed to close the path.
304 */
305bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000307
caryclark@google.comf1316942011-07-26 19:54:45 +0000308 int corners = 0;
309 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000310 first.set(0, 0);
311 last.set(0, 0);
312 int firstDirection = 0;
313 int lastDirection = 0;
314 int nextDirection = 0;
315 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000316 bool autoClose = false;
317 const uint8_t* verbs = fVerbs.begin();
318 const uint8_t* verbStop = fVerbs.end();
319 const SkPoint* pts = fPts.begin();
320 while (verbs != verbStop) {
321 switch (*verbs++) {
322 case kClose_Verb:
323 pts = fPts.begin();
324 autoClose = true;
325 case kLine_Verb: {
326 SkScalar left = last.fX;
327 SkScalar top = last.fY;
328 SkScalar right = pts->fX;
329 SkScalar bottom = pts->fY;
330 ++pts;
331 if (left != right && top != bottom) {
332 return false; // diagonal
333 }
334 if (left == right && top == bottom) {
335 break; // single point on side OK
336 }
337 nextDirection = (left != right) << 0 |
338 (left < right || top < bottom) << 1;
339 if (0 == corners) {
340 firstDirection = nextDirection;
341 first = last;
342 last = pts[-1];
343 corners = 1;
344 closedOrMoved = false;
345 break;
346 }
347 if (closedOrMoved) {
348 return false; // closed followed by a line
349 }
350 closedOrMoved = autoClose;
351 if (lastDirection != nextDirection) {
352 if (++corners > 4) {
353 return false; // too many direction changes
354 }
355 }
356 last = pts[-1];
357 if (lastDirection == nextDirection) {
358 break; // colinear segment
359 }
360 // Possible values for corners are 2, 3, and 4.
361 // When corners == 3, nextDirection opposes firstDirection.
362 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000363 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000364 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
365 if ((directionCycle ^ turn) != nextDirection) {
366 return false; // direction didn't follow cycle
367 }
368 break;
369 }
370 case kQuad_Verb:
371 case kCubic_Verb:
372 return false; // quadratic, cubic not allowed
373 case kMove_Verb:
374 last = *pts++;
375 closedOrMoved = true;
376 break;
377 }
378 lastDirection = nextDirection;
379 }
380 // Success if 4 corners and first point equals last
381 bool result = 4 == corners && first == last;
382 if (result && rect) {
383 *rect = getBounds();
384 }
385 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386}
387
388int SkPath::getPoints(SkPoint copy[], int max) const {
389 SkDEBUGCODE(this->validate();)
390
391 SkASSERT(max >= 0);
392 int count = fPts.count();
393 if (copy && max > 0 && count > 0) {
394 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
395 }
396 return count;
397}
398
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000399SkPoint SkPath::getPoint(int index) const {
400 if ((unsigned)index < (unsigned)fPts.count()) {
401 return fPts[index];
402 }
403 return SkPoint::Make(0, 0);
404}
405
reed@google.com294dd7b2011-10-11 11:58:32 +0000406bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407 SkDEBUGCODE(this->validate();)
408
reed@google.com294dd7b2011-10-11 11:58:32 +0000409 int count = fPts.count();
410 if (count > 0) {
411 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 *lastPt = fPts[count - 1];
413 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000414 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000416 if (lastPt) {
417 lastPt->set(0, 0);
418 }
419 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420}
421
422void SkPath::setLastPt(SkScalar x, SkScalar y) {
423 SkDEBUGCODE(this->validate();)
424
425 int count = fPts.count();
426 if (count == 0) {
427 this->moveTo(x, y);
428 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000429 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000431 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 }
433}
434
reed@android.comd252db02009-04-01 18:31:44 +0000435void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000437 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438
reed@android.comd252db02009-04-01 18:31:44 +0000439 fBoundsIsDirty = false;
440 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441}
442
reed@google.com04863fa2011-05-15 04:08:24 +0000443void SkPath::setConvexity(Convexity c) {
444 if (fConvexity != c) {
445 fConvexity = c;
446 GEN_ID_INC;
447 }
448}
449
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450//////////////////////////////////////////////////////////////////////////////
451// Construction methods
452
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000453#define DIRTY_AFTER_EDIT \
454 do { \
455 fBoundsIsDirty = true; \
456 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000457 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000458 } while (0)
459
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000460#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
461 do { \
462 fBoundsIsDirty = true; \
463 } while (0)
464
reed@android.com8a1c16f2008-12-17 15:59:43 +0000465void SkPath::incReserve(U16CPU inc) {
466 SkDEBUGCODE(this->validate();)
467
468 fVerbs.setReserve(fVerbs.count() + inc);
469 fPts.setReserve(fPts.count() + inc);
470
471 SkDEBUGCODE(this->validate();)
472}
473
474void SkPath::moveTo(SkScalar x, SkScalar y) {
475 SkDEBUGCODE(this->validate();)
476
477 int vc = fVerbs.count();
478 SkPoint* pt;
479
reed@google.comd335d1d2012-01-12 18:17:11 +0000480 // remember our index
481 fLastMoveToIndex = fPts.count();
482
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000483 pt = fPts.append();
484 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485 pt->set(x, y);
486
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000487 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000488 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489}
490
491void SkPath::rMoveTo(SkScalar x, SkScalar y) {
492 SkPoint pt;
493 this->getLastPt(&pt);
494 this->moveTo(pt.fX + x, pt.fY + y);
495}
496
reed@google.comd335d1d2012-01-12 18:17:11 +0000497void SkPath::injectMoveToIfNeeded() {
498 if (fLastMoveToIndex < 0) {
499 SkScalar x, y;
500 if (fVerbs.count() == 0) {
501 x = y = 0;
502 } else {
503 const SkPoint& pt = fPts[~fLastMoveToIndex];
504 x = pt.fX;
505 y = pt.fY;
506 }
507 this->moveTo(x, y);
508 }
509}
510
reed@android.com8a1c16f2008-12-17 15:59:43 +0000511void SkPath::lineTo(SkScalar x, SkScalar y) {
512 SkDEBUGCODE(this->validate();)
513
reed@google.comd335d1d2012-01-12 18:17:11 +0000514 this->injectMoveToIfNeeded();
515
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516 fPts.append()->set(x, y);
517 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000518 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000520 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000521 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522}
523
524void SkPath::rLineTo(SkScalar x, SkScalar y) {
525 SkPoint pt;
526 this->getLastPt(&pt);
527 this->lineTo(pt.fX + x, pt.fY + y);
528}
529
530void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
531 SkDEBUGCODE(this->validate();)
532
reed@google.comd335d1d2012-01-12 18:17:11 +0000533 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534
535 SkPoint* pts = fPts.append(2);
536 pts[0].set(x1, y1);
537 pts[1].set(x2, y2);
538 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000539 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000541 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000542 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000543}
544
545void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
546 SkPoint pt;
547 this->getLastPt(&pt);
548 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
549}
550
551void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
552 SkScalar x3, SkScalar y3) {
553 SkDEBUGCODE(this->validate();)
554
reed@google.comd335d1d2012-01-12 18:17:11 +0000555 this->injectMoveToIfNeeded();
556
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557 SkPoint* pts = fPts.append(3);
558 pts[0].set(x1, y1);
559 pts[1].set(x2, y2);
560 pts[2].set(x3, y3);
561 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000562 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000564 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000565 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566}
567
568void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
569 SkScalar x3, SkScalar y3) {
570 SkPoint pt;
571 this->getLastPt(&pt);
572 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
573 pt.fX + x3, pt.fY + y3);
574}
575
576void SkPath::close() {
577 SkDEBUGCODE(this->validate();)
578
579 int count = fVerbs.count();
580 if (count > 0) {
581 switch (fVerbs[count - 1]) {
582 case kLine_Verb:
583 case kQuad_Verb:
584 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000585 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000587 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 break;
589 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000590 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000591 break;
592 }
593 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000594
595 // signal that we need a moveTo to follow us (unless we're done)
596#if 0
597 if (fLastMoveToIndex >= 0) {
598 fLastMoveToIndex = ~fLastMoveToIndex;
599 }
600#else
601 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
602#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603}
604
605///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000606
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607void SkPath::addRect(const SkRect& rect, Direction dir) {
608 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
609}
610
611void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
612 SkScalar bottom, Direction dir) {
613 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
614
615 this->incReserve(5);
616
617 this->moveTo(left, top);
618 if (dir == kCCW_Direction) {
619 this->lineTo(left, bottom);
620 this->lineTo(right, bottom);
621 this->lineTo(right, top);
622 } else {
623 this->lineTo(right, top);
624 this->lineTo(right, bottom);
625 this->lineTo(left, bottom);
626 }
627 this->close();
628}
629
630#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
631
632void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
633 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 SkScalar w = rect.width();
635 SkScalar halfW = SkScalarHalf(w);
636 SkScalar h = rect.height();
637 SkScalar halfH = SkScalarHalf(h);
638
639 if (halfW <= 0 || halfH <= 0) {
640 return;
641 }
642
reed@google.comabf15c12011-01-18 20:35:51 +0000643 bool skip_hori = rx >= halfW;
644 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645
646 if (skip_hori && skip_vert) {
647 this->addOval(rect, dir);
648 return;
649 }
reed@google.comabf15c12011-01-18 20:35:51 +0000650
651 SkAutoPathBoundsUpdate apbu(this, rect);
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 if (skip_hori) {
654 rx = halfW;
655 } else if (skip_vert) {
656 ry = halfH;
657 }
658
659 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
660 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
661
662 this->incReserve(17);
663 this->moveTo(rect.fRight - rx, rect.fTop);
664 if (dir == kCCW_Direction) {
665 if (!skip_hori) {
666 this->lineTo(rect.fLeft + rx, rect.fTop); // top
667 }
668 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
669 rect.fLeft, rect.fTop + ry - sy,
670 rect.fLeft, rect.fTop + ry); // top-left
671 if (!skip_vert) {
672 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
673 }
674 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
675 rect.fLeft + rx - sx, rect.fBottom,
676 rect.fLeft + rx, rect.fBottom); // bot-left
677 if (!skip_hori) {
678 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
679 }
680 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
681 rect.fRight, rect.fBottom - ry + sy,
682 rect.fRight, rect.fBottom - ry); // bot-right
683 if (!skip_vert) {
684 this->lineTo(rect.fRight, rect.fTop + ry);
685 }
686 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
687 rect.fRight - rx + sx, rect.fTop,
688 rect.fRight - rx, rect.fTop); // top-right
689 } else {
690 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
691 rect.fRight, rect.fTop + ry - sy,
692 rect.fRight, rect.fTop + ry); // top-right
693 if (!skip_vert) {
694 this->lineTo(rect.fRight, rect.fBottom - ry);
695 }
696 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
697 rect.fRight - rx + sx, rect.fBottom,
698 rect.fRight - rx, rect.fBottom); // bot-right
699 if (!skip_hori) {
700 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
701 }
702 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
703 rect.fLeft, rect.fBottom - ry + sy,
704 rect.fLeft, rect.fBottom - ry); // bot-left
705 if (!skip_vert) {
706 this->lineTo(rect.fLeft, rect.fTop + ry); // left
707 }
708 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
709 rect.fLeft + rx - sx, rect.fTop,
710 rect.fLeft + rx, rect.fTop); // top-left
711 if (!skip_hori) {
712 this->lineTo(rect.fRight - rx, rect.fTop); // top
713 }
714 }
715 this->close();
716}
717
718static void add_corner_arc(SkPath* path, const SkRect& rect,
719 SkScalar rx, SkScalar ry, int startAngle,
720 SkPath::Direction dir, bool forceMoveTo) {
721 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
722 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000723
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 SkRect r;
725 r.set(-rx, -ry, rx, ry);
726
727 switch (startAngle) {
728 case 0:
729 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
730 break;
731 case 90:
732 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
733 break;
734 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
735 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000736 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 }
reed@google.comabf15c12011-01-18 20:35:51 +0000738
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739 SkScalar start = SkIntToScalar(startAngle);
740 SkScalar sweep = SkIntToScalar(90);
741 if (SkPath::kCCW_Direction == dir) {
742 start += sweep;
743 sweep = -sweep;
744 }
reed@google.comabf15c12011-01-18 20:35:51 +0000745
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 path->arcTo(r, start, sweep, forceMoveTo);
747}
748
749void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
750 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000751 // abort before we invoke SkAutoPathBoundsUpdate()
752 if (rect.isEmpty()) {
753 return;
754 }
755
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756 SkAutoPathBoundsUpdate apbu(this, rect);
757
758 if (kCW_Direction == dir) {
759 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
760 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
761 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
762 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
763 } else {
764 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
765 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
766 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
767 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
768 }
769 this->close();
770}
771
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000772bool SkPath::hasOnlyMoveTos() const {
773 const uint8_t* verbs = fVerbs.begin();
774 const uint8_t* verbStop = fVerbs.end();
775 while (verbs != verbStop) {
776 if (*verbs == kLine_Verb ||
777 *verbs == kQuad_Verb ||
778 *verbs == kCubic_Verb) {
779 return false;
780 }
781 ++verbs;
782 }
783 return true;
784}
785
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000787 /* If addOval() is called after previous moveTo(),
788 this path is still marked as an oval. This is used to
789 fit into WebKit's calling sequences.
790 We can't simply check isEmpty() in this case, as additional
791 moveTo() would mark the path non empty.
792 */
793 fIsOval = hasOnlyMoveTos();
794
795 SkAutoDisableOvalCheck adoc(this);
796
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkAutoPathBoundsUpdate apbu(this, oval);
798
799 SkScalar cx = oval.centerX();
800 SkScalar cy = oval.centerY();
801 SkScalar rx = SkScalarHalf(oval.width());
802 SkScalar ry = SkScalarHalf(oval.height());
803#if 0 // these seem faster than using quads (1/2 the number of edges)
804 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
805 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
806
807 this->incReserve(13);
808 this->moveTo(cx + rx, cy);
809 if (dir == kCCW_Direction) {
810 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
811 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
812 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
813 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
814 } else {
815 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
816 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
817 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
818 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
819 }
820#else
821 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
822 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
823 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
824 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
825
826 /*
827 To handle imprecision in computing the center and radii, we revert to
828 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
829 to ensure that we don't exceed the oval's bounds *ever*, since we want
830 to use oval for our fast-bounds, rather than have to recompute it.
831 */
832 const SkScalar L = oval.fLeft; // cx - rx
833 const SkScalar T = oval.fTop; // cy - ry
834 const SkScalar R = oval.fRight; // cx + rx
835 const SkScalar B = oval.fBottom; // cy + ry
836
837 this->incReserve(17); // 8 quads + close
838 this->moveTo(R, cy);
839 if (dir == kCCW_Direction) {
840 this->quadTo( R, cy - sy, cx + mx, cy - my);
841 this->quadTo(cx + sx, T, cx , T);
842 this->quadTo(cx - sx, T, cx - mx, cy - my);
843 this->quadTo( L, cy - sy, L, cy );
844 this->quadTo( L, cy + sy, cx - mx, cy + my);
845 this->quadTo(cx - sx, B, cx , B);
846 this->quadTo(cx + sx, B, cx + mx, cy + my);
847 this->quadTo( R, cy + sy, R, cy );
848 } else {
849 this->quadTo( R, cy + sy, cx + mx, cy + my);
850 this->quadTo(cx + sx, B, cx , B);
851 this->quadTo(cx - sx, B, cx - mx, cy + my);
852 this->quadTo( L, cy + sy, L, cy );
853 this->quadTo( L, cy - sy, cx - mx, cy - my);
854 this->quadTo(cx - sx, T, cx , T);
855 this->quadTo(cx + sx, T, cx + mx, cy - my);
856 this->quadTo( R, cy - sy, R, cy );
857 }
858#endif
859 this->close();
860}
861
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000862bool SkPath::isOval(SkRect* rect) const {
863 if (fIsOval && rect) {
864 *rect = getBounds();
865 }
866
867 return fIsOval;
868}
869
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
871 if (r > 0) {
872 SkRect rect;
873 rect.set(x - r, y - r, x + r, y + r);
874 this->addOval(rect, dir);
875 }
876}
877
878#include "SkGeometry.h"
879
880static int build_arc_points(const SkRect& oval, SkScalar startAngle,
881 SkScalar sweepAngle,
882 SkPoint pts[kSkBuildQuadArcStorage]) {
883 SkVector start, stop;
884
885 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
886 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
887 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000888
889 /* If the sweep angle is nearly (but less than) 360, then due to precision
890 loss in radians-conversion and/or sin/cos, we may end up with coincident
891 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
892 of drawing a nearly complete circle (good).
893 e.g. canvas.drawArc(0, 359.99, ...)
894 -vs- canvas.drawArc(0, 359.9, ...)
895 We try to detect this edge case, and tweak the stop vector
896 */
897 if (start == stop) {
898 SkScalar sw = SkScalarAbs(sweepAngle);
899 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
900 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
901 // make a guess at a tiny angle (in radians) to tweak by
902 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
903 // not sure how much will be enough, so we use a loop
904 do {
905 stopRad -= deltaRad;
906 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
907 } while (start == stop);
908 }
909 }
910
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000912
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
914 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000915
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916 return SkBuildQuadArc(start, stop,
917 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
918 &matrix, pts);
919}
920
921void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
922 bool forceMoveTo) {
923 if (oval.width() < 0 || oval.height() < 0) {
924 return;
925 }
926
927 SkPoint pts[kSkBuildQuadArcStorage];
928 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
929 SkASSERT((count & 1) == 1);
930
931 if (fVerbs.count() == 0) {
932 forceMoveTo = true;
933 }
934 this->incReserve(count);
935 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
936 for (int i = 1; i < count; i += 2) {
937 this->quadTo(pts[i], pts[i+1]);
938 }
939}
940
941void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
942 SkScalar sweepAngle) {
943 if (oval.isEmpty() || 0 == sweepAngle) {
944 return;
945 }
946
947 const SkScalar kFullCircleAngle = SkIntToScalar(360);
948
949 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
950 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
951 return;
952 }
953
954 SkPoint pts[kSkBuildQuadArcStorage];
955 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
956
957 this->incReserve(count);
958 this->moveTo(pts[0]);
959 for (int i = 1; i < count; i += 2) {
960 this->quadTo(pts[i], pts[i+1]);
961 }
962}
963
964/*
965 Need to handle the case when the angle is sharp, and our computed end-points
966 for the arc go behind pt1 and/or p2...
967*/
968void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
969 SkScalar radius) {
970 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000971
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 // need to know our prev pt so we can construct tangent vectors
973 {
974 SkPoint start;
975 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000976 // Handle degenerate cases by adding a line to the first point and
977 // bailing out.
978 if ((x1 == start.fX && y1 == start.fY) ||
979 (x1 == x2 && y1 == y2) ||
980 radius == 0) {
981 this->lineTo(x1, y1);
982 return;
983 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 before.setNormalize(x1 - start.fX, y1 - start.fY);
985 after.setNormalize(x2 - x1, y2 - y1);
986 }
reed@google.comabf15c12011-01-18 20:35:51 +0000987
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 SkScalar cosh = SkPoint::DotProduct(before, after);
989 SkScalar sinh = SkPoint::CrossProduct(before, after);
990
991 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000992 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 return;
994 }
reed@google.comabf15c12011-01-18 20:35:51 +0000995
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
997 if (dist < 0) {
998 dist = -dist;
999 }
1000
1001 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1002 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1003 SkRotationDirection arcDir;
1004
1005 // now turn before/after into normals
1006 if (sinh > 0) {
1007 before.rotateCCW();
1008 after.rotateCCW();
1009 arcDir = kCW_SkRotationDirection;
1010 } else {
1011 before.rotateCW();
1012 after.rotateCW();
1013 arcDir = kCCW_SkRotationDirection;
1014 }
1015
1016 SkMatrix matrix;
1017 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001018
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 matrix.setScale(radius, radius);
1020 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1021 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001022
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001024
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 this->incReserve(count);
1026 // [xx,yy] == pts[0]
1027 this->lineTo(xx, yy);
1028 for (int i = 1; i < count; i += 2) {
1029 this->quadTo(pts[i], pts[i+1]);
1030 }
1031}
1032
1033///////////////////////////////////////////////////////////////////////////////
1034
1035void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1036 SkMatrix matrix;
1037
1038 matrix.setTranslate(dx, dy);
1039 this->addPath(path, matrix);
1040}
1041
1042void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1043 this->incReserve(path.fPts.count());
1044
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001045 fIsOval = false;
1046
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001047 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001048 SkPoint pts[4];
1049 Verb verb;
1050
1051 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1052
1053 while ((verb = iter.next(pts)) != kDone_Verb) {
1054 switch (verb) {
1055 case kMove_Verb:
1056 proc(matrix, &pts[0], &pts[0], 1);
1057 this->moveTo(pts[0]);
1058 break;
1059 case kLine_Verb:
1060 proc(matrix, &pts[1], &pts[1], 1);
1061 this->lineTo(pts[1]);
1062 break;
1063 case kQuad_Verb:
1064 proc(matrix, &pts[1], &pts[1], 2);
1065 this->quadTo(pts[1], pts[2]);
1066 break;
1067 case kCubic_Verb:
1068 proc(matrix, &pts[1], &pts[1], 3);
1069 this->cubicTo(pts[1], pts[2], pts[3]);
1070 break;
1071 case kClose_Verb:
1072 this->close();
1073 break;
1074 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001075 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 }
1077 }
1078}
1079
1080///////////////////////////////////////////////////////////////////////////////
1081
1082static const uint8_t gPtsInVerb[] = {
1083 1, // kMove
1084 1, // kLine
1085 2, // kQuad
1086 3, // kCubic
1087 0, // kClose
1088 0 // kDone
1089};
1090
1091// ignore the initial moveto, and stop when the 1st contour ends
1092void SkPath::pathTo(const SkPath& path) {
1093 int i, vcount = path.fVerbs.count();
1094 if (vcount == 0) {
1095 return;
1096 }
1097
1098 this->incReserve(vcount);
1099
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001100 fIsOval = false;
1101
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102 const uint8_t* verbs = path.fVerbs.begin();
1103 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1104
1105 SkASSERT(verbs[0] == kMove_Verb);
1106 for (i = 1; i < vcount; i++) {
1107 switch (verbs[i]) {
1108 case kLine_Verb:
1109 this->lineTo(pts[0].fX, pts[0].fY);
1110 break;
1111 case kQuad_Verb:
1112 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1113 break;
1114 case kCubic_Verb:
1115 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1116 pts[2].fX, pts[2].fY);
1117 break;
1118 case kClose_Verb:
1119 return;
1120 }
1121 pts += gPtsInVerb[verbs[i]];
1122 }
1123}
1124
1125// ignore the last point of the 1st contour
1126void SkPath::reversePathTo(const SkPath& path) {
1127 int i, vcount = path.fVerbs.count();
1128 if (vcount == 0) {
1129 return;
1130 }
1131
1132 this->incReserve(vcount);
1133
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001134 fIsOval = false;
1135
reed@android.com8a1c16f2008-12-17 15:59:43 +00001136 const uint8_t* verbs = path.fVerbs.begin();
1137 const SkPoint* pts = path.fPts.begin();
1138
1139 SkASSERT(verbs[0] == kMove_Verb);
1140 for (i = 1; i < vcount; i++) {
1141 int n = gPtsInVerb[verbs[i]];
1142 if (n == 0) {
1143 break;
1144 }
1145 pts += n;
1146 }
1147
1148 while (--i > 0) {
1149 switch (verbs[i]) {
1150 case kLine_Verb:
1151 this->lineTo(pts[-1].fX, pts[-1].fY);
1152 break;
1153 case kQuad_Verb:
1154 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1155 break;
1156 case kCubic_Verb:
1157 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1158 pts[-3].fX, pts[-3].fY);
1159 break;
1160 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001161 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 break;
1163 }
1164 pts -= gPtsInVerb[verbs[i]];
1165 }
1166}
1167
reed@google.com63d73742012-01-10 15:33:12 +00001168void SkPath::reverseAddPath(const SkPath& src) {
1169 this->incReserve(src.fPts.count());
1170
1171 const SkPoint* startPts = src.fPts.begin();
1172 const SkPoint* pts = src.fPts.end();
1173 const uint8_t* startVerbs = src.fVerbs.begin();
1174 const uint8_t* verbs = src.fVerbs.end();
1175
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001176 fIsOval = false;
1177
reed@google.com63d73742012-01-10 15:33:12 +00001178 bool needMove = true;
1179 bool needClose = false;
1180 while (verbs > startVerbs) {
1181 uint8_t v = *--verbs;
1182 int n = gPtsInVerb[v];
1183
1184 if (needMove) {
1185 --pts;
1186 this->moveTo(pts->fX, pts->fY);
1187 needMove = false;
1188 }
1189 pts -= n;
1190 switch (v) {
1191 case kMove_Verb:
1192 if (needClose) {
1193 this->close();
1194 needClose = false;
1195 }
1196 needMove = true;
1197 pts += 1; // so we see the point in "if (needMove)" above
1198 break;
1199 case kLine_Verb:
1200 this->lineTo(pts[0]);
1201 break;
1202 case kQuad_Verb:
1203 this->quadTo(pts[1], pts[0]);
1204 break;
1205 case kCubic_Verb:
1206 this->cubicTo(pts[2], pts[1], pts[0]);
1207 break;
1208 case kClose_Verb:
1209 needClose = true;
1210 break;
1211 default:
1212 SkASSERT(!"unexpected verb");
1213 }
1214 }
1215}
1216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217///////////////////////////////////////////////////////////////////////////////
1218
1219void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1220 SkMatrix matrix;
1221
1222 matrix.setTranslate(dx, dy);
1223 this->transform(matrix, dst);
1224}
1225
1226#include "SkGeometry.h"
1227
1228static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1229 int level = 2) {
1230 if (--level >= 0) {
1231 SkPoint tmp[5];
1232
1233 SkChopQuadAtHalf(pts, tmp);
1234 subdivide_quad_to(path, &tmp[0], level);
1235 subdivide_quad_to(path, &tmp[2], level);
1236 } else {
1237 path->quadTo(pts[1], pts[2]);
1238 }
1239}
1240
1241static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1242 int level = 2) {
1243 if (--level >= 0) {
1244 SkPoint tmp[7];
1245
1246 SkChopCubicAtHalf(pts, tmp);
1247 subdivide_cubic_to(path, &tmp[0], level);
1248 subdivide_cubic_to(path, &tmp[3], level);
1249 } else {
1250 path->cubicTo(pts[1], pts[2], pts[3]);
1251 }
1252}
1253
1254void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1255 SkDEBUGCODE(this->validate();)
1256 if (dst == NULL) {
1257 dst = (SkPath*)this;
1258 }
1259
tomhudson@google.com8d430182011-06-06 19:11:19 +00001260 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 SkPath tmp;
1262 tmp.fFillType = fFillType;
1263
1264 SkPath::Iter iter(*this, false);
1265 SkPoint pts[4];
1266 SkPath::Verb verb;
1267
reed@google.com4a3b7142012-05-16 17:16:46 +00001268 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 switch (verb) {
1270 case kMove_Verb:
1271 tmp.moveTo(pts[0]);
1272 break;
1273 case kLine_Verb:
1274 tmp.lineTo(pts[1]);
1275 break;
1276 case kQuad_Verb:
1277 subdivide_quad_to(&tmp, pts);
1278 break;
1279 case kCubic_Verb:
1280 subdivide_cubic_to(&tmp, pts);
1281 break;
1282 case kClose_Verb:
1283 tmp.close();
1284 break;
1285 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001286 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 break;
1288 }
1289 }
1290
1291 dst->swap(tmp);
1292 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1293 } else {
1294 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001295 // fBoundsIsDirty before we set it
1296 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001298 matrix.mapRect(&dst->fBounds, fBounds);
1299 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001301 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001302 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 }
1304
1305 if (this != dst) {
1306 dst->fVerbs = fVerbs;
1307 dst->fPts.setCount(fPts.count());
1308 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001309 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001310 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001311 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001313
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001315
1316 if (fIsOval) {
1317 // It's an oval only if it stays a rect.
1318 dst->fIsOval = matrix.rectStaysRect();
1319 }
1320
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 SkDEBUGCODE(dst->validate();)
1322 }
1323}
1324
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325///////////////////////////////////////////////////////////////////////////////
1326///////////////////////////////////////////////////////////////////////////////
1327
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001328enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001329 kEmptyContour_SegmentState, // The current contour is empty. We may be
1330 // starting processing or we may have just
1331 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001332 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1333 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1334 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335};
1336
1337SkPath::Iter::Iter() {
1338#ifdef SK_DEBUG
1339 fPts = NULL;
1340 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001341 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001342 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343#endif
1344 // need to init enough to make next() harmlessly return kDone_Verb
1345 fVerbs = NULL;
1346 fVerbStop = NULL;
1347 fNeedClose = false;
1348}
1349
1350SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1351 this->setPath(path, forceClose);
1352}
1353
1354void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1355 fPts = path.fPts.begin();
1356 fVerbs = path.fVerbs.begin();
1357 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001358 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001359 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 fForceClose = SkToU8(forceClose);
1361 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001362 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363}
1364
1365bool SkPath::Iter::isClosedContour() const {
1366 if (fVerbs == NULL || fVerbs == fVerbStop) {
1367 return false;
1368 }
1369 if (fForceClose) {
1370 return true;
1371 }
1372
1373 const uint8_t* verbs = fVerbs;
1374 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001375
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376 if (kMove_Verb == *verbs) {
1377 verbs += 1; // skip the initial moveto
1378 }
1379
1380 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001381 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 if (kMove_Verb == v) {
1383 break;
1384 }
1385 if (kClose_Verb == v) {
1386 return true;
1387 }
1388 }
1389 return false;
1390}
1391
1392SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001393 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001395 // A special case: if both points are NaN, SkPoint::operation== returns
1396 // false, but the iterator expects that they are treated as the same.
1397 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001398 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1399 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001400 return kClose_Verb;
1401 }
1402
reed@google.com9e25dbf2012-05-15 17:05:38 +00001403 pts[0] = fLastPt;
1404 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 fLastPt = fMoveTo;
1406 fCloseLine = true;
1407 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001408 } else {
1409 pts[0] = fMoveTo;
1410 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412}
1413
reed@google.com9e25dbf2012-05-15 17:05:38 +00001414const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001415 if (fSegmentState == kAfterMove_SegmentState) {
1416 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001417 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001418 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001420 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1421 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001422 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424}
1425
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001426void SkPath::Iter::consumeDegenerateSegments() {
1427 // We need to step over anything that will not move the current draw point
1428 // forward before the next move is seen
1429 const uint8_t* lastMoveVerb = 0;
1430 const SkPoint* lastMovePt = 0;
1431 SkPoint lastPt = fLastPt;
1432 while (fVerbs != fVerbStop) {
1433 unsigned verb = *fVerbs;
1434 switch (verb) {
1435 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001436 // Keep a record of this most recent move
1437 lastMoveVerb = fVerbs;
1438 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001439 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001440 fVerbs++;
1441 fPts++;
1442 break;
1443
1444 case kClose_Verb:
1445 // A close when we are in a segment is always valid
1446 if (fSegmentState == kAfterPrimitive_SegmentState) {
1447 return;
1448 }
1449 // A close at any other time must be ignored
1450 fVerbs++;
1451 break;
1452
1453 case kLine_Verb:
1454 if (!IsLineDegenerate(lastPt, fPts[0])) {
1455 if (lastMoveVerb) {
1456 fVerbs = lastMoveVerb;
1457 fPts = lastMovePt;
1458 return;
1459 }
1460 return;
1461 }
1462 // Ignore this line and continue
1463 fVerbs++;
1464 fPts++;
1465 break;
1466
1467 case kQuad_Verb:
1468 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1469 if (lastMoveVerb) {
1470 fVerbs = lastMoveVerb;
1471 fPts = lastMovePt;
1472 return;
1473 }
1474 return;
1475 }
1476 // Ignore this line and continue
1477 fVerbs++;
1478 fPts += 2;
1479 break;
1480
1481 case kCubic_Verb:
1482 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1483 if (lastMoveVerb) {
1484 fVerbs = lastMoveVerb;
1485 fPts = lastMovePt;
1486 return;
1487 }
1488 return;
1489 }
1490 // Ignore this line and continue
1491 fVerbs++;
1492 fPts += 3;
1493 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001494
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001495 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001496 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001497 }
1498 }
1499}
1500
reed@google.com4a3b7142012-05-16 17:16:46 +00001501SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001502 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001503
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001505 // Close the curve if requested and if there is some curve to close
1506 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001507 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 return kLine_Verb;
1509 }
1510 fNeedClose = false;
1511 return kClose_Verb;
1512 }
1513 return kDone_Verb;
1514 }
1515
1516 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001517 const SkPoint* SK_RESTRICT srcPts = fPts;
1518 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519
1520 switch (verb) {
1521 case kMove_Verb:
1522 if (fNeedClose) {
1523 fVerbs -= 1;
1524 verb = this->autoClose(pts);
1525 if (verb == kClose_Verb) {
1526 fNeedClose = false;
1527 }
1528 return (Verb)verb;
1529 }
1530 if (fVerbs == fVerbStop) { // might be a trailing moveto
1531 return kDone_Verb;
1532 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001533 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001534 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001536 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001537 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 fNeedClose = fForceClose;
1539 break;
1540 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001541 pts[0] = this->cons_moveTo();
1542 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 fLastPt = srcPts[0];
1544 fCloseLine = false;
1545 srcPts += 1;
1546 break;
1547 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001548 pts[0] = this->cons_moveTo();
1549 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 fLastPt = srcPts[1];
1551 srcPts += 2;
1552 break;
1553 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001554 pts[0] = this->cons_moveTo();
1555 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 fLastPt = srcPts[2];
1557 srcPts += 3;
1558 break;
1559 case kClose_Verb:
1560 verb = this->autoClose(pts);
1561 if (verb == kLine_Verb) {
1562 fVerbs -= 1;
1563 } else {
1564 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001565 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001567 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 break;
1569 }
1570 fPts = srcPts;
1571 return (Verb)verb;
1572}
1573
1574///////////////////////////////////////////////////////////////////////////////
1575
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001576SkPath::RawIter::RawIter() {
1577#ifdef SK_DEBUG
1578 fPts = NULL;
1579 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1580#endif
1581 // need to init enough to make next() harmlessly return kDone_Verb
1582 fVerbs = NULL;
1583 fVerbStop = NULL;
1584}
1585
1586SkPath::RawIter::RawIter(const SkPath& path) {
1587 this->setPath(path);
1588}
1589
1590void SkPath::RawIter::setPath(const SkPath& path) {
1591 fPts = path.fPts.begin();
1592 fVerbs = path.fVerbs.begin();
1593 fVerbStop = path.fVerbs.end();
1594 fMoveTo.fX = fMoveTo.fY = 0;
1595 fLastPt.fX = fLastPt.fY = 0;
1596}
1597
1598SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1599 if (fVerbs == fVerbStop) {
1600 return kDone_Verb;
1601 }
1602
1603 unsigned verb = *fVerbs++;
1604 const SkPoint* srcPts = fPts;
1605
1606 switch (verb) {
1607 case kMove_Verb:
1608 if (pts) {
1609 pts[0] = *srcPts;
1610 }
1611 fMoveTo = srcPts[0];
1612 fLastPt = fMoveTo;
1613 srcPts += 1;
1614 break;
1615 case kLine_Verb:
1616 if (pts) {
1617 pts[0] = fLastPt;
1618 pts[1] = srcPts[0];
1619 }
1620 fLastPt = srcPts[0];
1621 srcPts += 1;
1622 break;
1623 case kQuad_Verb:
1624 if (pts) {
1625 pts[0] = fLastPt;
1626 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1627 }
1628 fLastPt = srcPts[1];
1629 srcPts += 2;
1630 break;
1631 case kCubic_Verb:
1632 if (pts) {
1633 pts[0] = fLastPt;
1634 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1635 }
1636 fLastPt = srcPts[2];
1637 srcPts += 3;
1638 break;
1639 case kClose_Verb:
1640 fLastPt = fMoveTo;
1641 if (pts) {
1642 pts[0] = fMoveTo;
1643 }
1644 break;
1645 }
1646 fPts = srcPts;
1647 return (Verb)verb;
1648}
1649
1650///////////////////////////////////////////////////////////////////////////////
1651
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652/*
1653 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1654*/
1655
reed@google.com73945652011-04-25 19:04:27 +00001656void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657 SkDEBUGCODE(this->validate();)
1658
1659 buffer.write32(fPts.count());
1660 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001661 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1663 buffer.writePad(fVerbs.begin(), fVerbs.count());
1664}
1665
reed@google.com73945652011-04-25 19:04:27 +00001666void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 fPts.setCount(buffer.readS32());
1668 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001669 uint32_t packed = buffer.readS32();
1670 fFillType = packed >> 8;
1671 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1673 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001674
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001675 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001676 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001677
1678 SkDEBUGCODE(this->validate();)
1679}
1680
1681///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001682
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683void SkPath::dump(bool forceClose, const char title[]) const {
1684 Iter iter(*this, forceClose);
1685 SkPoint pts[4];
1686 Verb verb;
1687
1688 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1689 title ? title : "");
1690
reed@google.com4a3b7142012-05-16 17:16:46 +00001691 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 switch (verb) {
1693 case kMove_Verb:
1694#ifdef SK_CAN_USE_FLOAT
1695 SkDebugf(" path: moveTo [%g %g]\n",
1696 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1697#else
1698 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1699#endif
1700 break;
1701 case kLine_Verb:
1702#ifdef SK_CAN_USE_FLOAT
1703 SkDebugf(" path: lineTo [%g %g]\n",
1704 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1705#else
1706 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1707#endif
1708 break;
1709 case kQuad_Verb:
1710#ifdef SK_CAN_USE_FLOAT
1711 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1712 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1713 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1714#else
1715 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1716 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1717#endif
1718 break;
1719 case kCubic_Verb:
1720#ifdef SK_CAN_USE_FLOAT
1721 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1722 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1723 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1724 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1725#else
1726 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1727 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1728 pts[3].fX, pts[3].fY);
1729#endif
1730 break;
1731 case kClose_Verb:
1732 SkDebugf(" path: close\n");
1733 break;
1734 default:
1735 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1736 verb = kDone_Verb; // stop the loop
1737 break;
1738 }
1739 }
1740 SkDebugf("path: done %s\n", title ? title : "");
1741}
1742
reed@android.come522ca52009-11-23 20:10:41 +00001743void SkPath::dump() const {
1744 this->dump(false);
1745}
1746
1747#ifdef SK_DEBUG
1748void SkPath::validate() const {
1749 SkASSERT(this != NULL);
1750 SkASSERT((fFillType & ~3) == 0);
1751 fPts.validate();
1752 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001753
reed@android.come522ca52009-11-23 20:10:41 +00001754 if (!fBoundsIsDirty) {
1755 SkRect bounds;
1756 compute_pt_bounds(&bounds, fPts);
1757 if (fPts.count() <= 1) {
1758 // if we're empty, fBounds may be empty but translated, so we can't
1759 // necessarily compare to bounds directly
1760 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1761 // be [2, 2, 2, 2]
1762 SkASSERT(bounds.isEmpty());
1763 SkASSERT(fBounds.isEmpty());
1764 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001765 if (bounds.isEmpty()) {
1766 SkASSERT(fBounds.isEmpty());
1767 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001768 if (!fBounds.isEmpty()) {
1769 SkASSERT(fBounds.contains(bounds));
1770 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001771 }
reed@android.come522ca52009-11-23 20:10:41 +00001772 }
1773 }
reed@google.com10296cc2011-09-21 12:29:05 +00001774
1775 uint32_t mask = 0;
1776 for (int i = 0; i < fVerbs.count(); i++) {
1777 switch (fVerbs[i]) {
1778 case kLine_Verb:
1779 mask |= kLine_SegmentMask;
1780 break;
1781 case kQuad_Verb:
1782 mask |= kQuad_SegmentMask;
1783 break;
1784 case kCubic_Verb:
1785 mask |= kCubic_SegmentMask;
1786 }
1787 }
1788 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001789}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790#endif
reed@android.come522ca52009-11-23 20:10:41 +00001791
reed@google.com04863fa2011-05-15 04:08:24 +00001792///////////////////////////////////////////////////////////////////////////////
1793
reed@google.com0b7b9822011-05-16 12:29:27 +00001794static int sign(SkScalar x) { return x < 0; }
1795#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001796
reed@google.com04863fa2011-05-15 04:08:24 +00001797static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001798 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001799}
1800
1801// only valid for a single contour
1802struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001803 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001804 fSign = 0;
1805 // warnings
1806 fCurrPt.set(0, 0);
1807 fVec0.set(0, 0);
1808 fVec1.set(0, 0);
1809 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001810
1811 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001812 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001813 }
1814
1815 SkPath::Convexity getConvexity() const { return fConvexity; }
1816
1817 void addPt(const SkPoint& pt) {
1818 if (SkPath::kConcave_Convexity == fConvexity) {
1819 return;
1820 }
1821
1822 if (0 == fPtCount) {
1823 fCurrPt = pt;
1824 ++fPtCount;
1825 } else {
1826 SkVector vec = pt - fCurrPt;
1827 if (vec.fX || vec.fY) {
1828 fCurrPt = pt;
1829 if (++fPtCount == 2) {
1830 fFirstVec = fVec1 = vec;
1831 } else {
1832 SkASSERT(fPtCount > 2);
1833 this->addVec(vec);
1834 }
reed@google.com85b6e392011-05-15 20:25:17 +00001835
1836 int sx = sign(vec.fX);
1837 int sy = sign(vec.fY);
1838 fDx += (sx != fSx);
1839 fDy += (sy != fSy);
1840 fSx = sx;
1841 fSy = sy;
1842
1843 if (fDx > 3 || fDy > 3) {
1844 fConvexity = SkPath::kConcave_Convexity;
1845 }
reed@google.com04863fa2011-05-15 04:08:24 +00001846 }
1847 }
1848 }
1849
1850 void close() {
1851 if (fPtCount > 2) {
1852 this->addVec(fFirstVec);
1853 }
1854 }
1855
1856private:
1857 void addVec(const SkVector& vec) {
1858 SkASSERT(vec.fX || vec.fY);
1859 fVec0 = fVec1;
1860 fVec1 = vec;
1861 int sign = CrossProductSign(fVec0, fVec1);
1862 if (0 == fSign) {
1863 fSign = sign;
1864 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001865 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001866 fConvexity = SkPath::kConcave_Convexity;
1867 }
1868 }
1869 }
1870
1871 SkPoint fCurrPt;
1872 SkVector fVec0, fVec1, fFirstVec;
1873 int fPtCount; // non-degenerate points
1874 int fSign;
1875 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001876 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001877};
1878
1879SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1880 SkPoint pts[4];
1881 SkPath::Verb verb;
1882 SkPath::Iter iter(path, true);
1883
1884 int contourCount = 0;
1885 int count;
1886 Convexicator state;
1887
1888 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1889 switch (verb) {
1890 case kMove_Verb:
1891 if (++contourCount > 1) {
1892 return kConcave_Convexity;
1893 }
1894 pts[1] = pts[0];
1895 count = 1;
1896 break;
1897 case kLine_Verb: count = 1; break;
1898 case kQuad_Verb: count = 2; break;
1899 case kCubic_Verb: count = 3; break;
1900 case kClose_Verb:
1901 state.close();
1902 count = 0;
1903 break;
1904 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001905 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001906 return kConcave_Convexity;
1907 }
1908
1909 for (int i = 1; i <= count; i++) {
1910 state.addPt(pts[i]);
1911 }
1912 // early exit
1913 if (kConcave_Convexity == state.getConvexity()) {
1914 return kConcave_Convexity;
1915 }
1916 }
1917 return state.getConvexity();
1918}
reed@google.com69a99432012-01-10 18:00:10 +00001919
1920///////////////////////////////////////////////////////////////////////////////
1921
1922class ContourIter {
1923public:
1924 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1925
1926 bool done() const { return fDone; }
1927 // if !done() then these may be called
1928 int count() const { return fCurrPtCount; }
1929 const SkPoint* pts() const { return fCurrPt; }
1930 void next();
1931
1932private:
1933 int fCurrPtCount;
1934 const SkPoint* fCurrPt;
1935 const uint8_t* fCurrVerb;
1936 const uint8_t* fStopVerbs;
1937 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001938 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001939};
1940
1941ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1942 const SkTDArray<SkPoint>& pts) {
1943 fStopVerbs = verbs.begin() + verbs.count();
1944
1945 fDone = false;
1946 fCurrPt = pts.begin();
1947 fCurrVerb = verbs.begin();
1948 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001949 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001950 this->next();
1951}
1952
1953void ContourIter::next() {
1954 if (fCurrVerb >= fStopVerbs) {
1955 fDone = true;
1956 }
1957 if (fDone) {
1958 return;
1959 }
1960
1961 // skip pts of prev contour
1962 fCurrPt += fCurrPtCount;
1963
1964 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1965 int ptCount = 1; // moveTo
1966 const uint8_t* verbs = fCurrVerb;
1967
1968 for (++verbs; verbs < fStopVerbs; ++verbs) {
1969 switch (*verbs) {
1970 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001971 goto CONTOUR_END;
1972 case SkPath::kLine_Verb:
1973 ptCount += 1;
1974 break;
1975 case SkPath::kQuad_Verb:
1976 ptCount += 2;
1977 break;
1978 case SkPath::kCubic_Verb:
1979 ptCount += 3;
1980 break;
1981 default: // kClose_Verb, just keep going
1982 break;
1983 }
1984 }
1985CONTOUR_END:
1986 fCurrPtCount = ptCount;
1987 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001988 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001989}
1990
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001991// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00001992static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001993 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
1994 // We may get 0 when the above subtracts underflow. We expect this to be
1995 // very rare and lazily promote to double.
1996 if (0 == cross) {
1997 double p0x = SkScalarToDouble(p0.fX);
1998 double p0y = SkScalarToDouble(p0.fY);
1999
2000 double p1x = SkScalarToDouble(p1.fX);
2001 double p1y = SkScalarToDouble(p1.fY);
2002
2003 double p2x = SkScalarToDouble(p2.fX);
2004 double p2y = SkScalarToDouble(p2.fY);
2005
2006 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2007 (p1y - p0y) * (p2x - p0x));
2008
2009 }
2010 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002011}
2012
reed@google.comc1ea60a2012-01-31 15:15:36 +00002013// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002014static int find_max_y(const SkPoint pts[], int count) {
2015 SkASSERT(count > 0);
2016 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002017 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002018 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002019 SkScalar y = pts[i].fY;
2020 if (y > max) {
2021 max = y;
2022 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002023 }
2024 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002025 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002026}
2027
reed@google.comcabaf1d2012-01-11 21:03:05 +00002028static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2029 int i = index;
2030 for (;;) {
2031 i = (i + inc) % n;
2032 if (i == index) { // we wrapped around, so abort
2033 break;
2034 }
2035 if (pts[index] != pts[i]) { // found a different point, success!
2036 break;
2037 }
2038 }
2039 return i;
2040}
2041
reed@google.comc1ea60a2012-01-31 15:15:36 +00002042/**
2043 * Starting at index, and moving forward (incrementing), find the xmin and
2044 * xmax of the contiguous points that have the same Y.
2045 */
2046static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2047 int* maxIndexPtr) {
2048 const SkScalar y = pts[index].fY;
2049 SkScalar min = pts[index].fX;
2050 SkScalar max = min;
2051 int minIndex = index;
2052 int maxIndex = index;
2053 for (int i = index + 1; i < n; ++i) {
2054 if (pts[i].fY != y) {
2055 break;
2056 }
2057 SkScalar x = pts[i].fX;
2058 if (x < min) {
2059 min = x;
2060 minIndex = i;
2061 } else if (x > max) {
2062 max = x;
2063 maxIndex = i;
2064 }
2065 }
2066 *maxIndexPtr = maxIndex;
2067 return minIndex;
2068}
2069
reed@google.comac8543f2012-01-30 20:51:25 +00002070static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2071 if (dir) {
2072 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2073 }
2074 return true;
2075}
2076
reed@google.comc1ea60a2012-01-31 15:15:36 +00002077#if 0
2078#include "SkString.h"
2079#include "../utils/SkParsePath.h"
2080static void dumpPath(const SkPath& path) {
2081 SkString str;
2082 SkParsePath::ToSVGString(path, &str);
2083 SkDebugf("%s\n", str.c_str());
2084}
2085#endif
2086
reed@google.comac8543f2012-01-30 20:51:25 +00002087/*
2088 * We loop through all contours, and keep the computed cross-product of the
2089 * contour that contained the global y-max. If we just look at the first
2090 * contour, we may find one that is wound the opposite way (correctly) since
2091 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2092 * that is outer most (or at least has the global y-max) before we can consider
2093 * its cross product.
2094 */
reed@google.com69a99432012-01-10 18:00:10 +00002095bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002096// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002097 // don't want to pay the cost for computing this if it
2098 // is unknown, so we don't call isConvex()
2099 const Convexity conv = this->getConvexityOrUnknown();
2100
2101 ContourIter iter(fVerbs, fPts);
2102
reed@google.comac8543f2012-01-30 20:51:25 +00002103 // initialize with our logical y-min
2104 SkScalar ymax = this->getBounds().fTop;
2105 SkScalar ymaxCross = 0;
2106
reed@google.com69a99432012-01-10 18:00:10 +00002107 for (; !iter.done(); iter.next()) {
2108 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002109 if (n < 3) {
2110 continue;
2111 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002112
reed@google.comcabaf1d2012-01-11 21:03:05 +00002113 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002114 SkScalar cross = 0;
2115 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002116 // we loop, skipping over degenerate or flat segments that will
2117 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002118 for (int i = 0; i < n - 2; ++i) {
2119 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2120 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002121 // early-exit, as kConvex is assumed to have only 1
2122 // non-degenerate contour
2123 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002124 }
2125 }
2126 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002127 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002128 if (pts[index].fY < ymax) {
2129 continue;
2130 }
2131
reed@google.comc1ea60a2012-01-31 15:15:36 +00002132 // If there is more than 1 distinct point at the y-max, we take the
2133 // x-min and x-max of them and just subtract to compute the dir.
2134 if (pts[(index + 1) % n].fY == pts[index].fY) {
2135 int maxIndex;
2136 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002137 if (minIndex == maxIndex) {
2138 goto TRY_CROSSPROD;
2139 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002140 SkASSERT(pts[minIndex].fY == pts[index].fY);
2141 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2142 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2143 // we just subtract the indices, and let that auto-convert to
2144 // SkScalar, since we just want - or + to signal the direction.
2145 cross = minIndex - maxIndex;
2146 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002147 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002148 // Find a next and prev index to use for the cross-product test,
2149 // but we try to find pts that form non-zero vectors from pts[index]
2150 //
2151 // Its possible that we can't find two non-degenerate vectors, so
2152 // we have to guard our search (e.g. all the pts could be in the
2153 // same place).
2154
2155 // we pass n - 1 instead of -1 so we don't foul up % operator by
2156 // passing it a negative LH argument.
2157 int prev = find_diff_pt(pts, index, n, n - 1);
2158 if (prev == index) {
2159 // completely degenerate, skip to next contour
2160 continue;
2161 }
2162 int next = find_diff_pt(pts, index, n, 1);
2163 SkASSERT(next != index);
2164 cross = cross_prod(pts[prev], pts[index], pts[next]);
2165 // if we get a zero, but the pts aren't on top of each other, then
2166 // we can just look at the direction
2167 if (0 == cross) {
2168 // construct the subtract so we get the correct Direction below
2169 cross = pts[index].fX - pts[next].fX;
2170 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002171 }
reed@google.comac8543f2012-01-30 20:51:25 +00002172
2173 if (cross) {
2174 // record our best guess so far
2175 ymax = pts[index].fY;
2176 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002177 }
reed@google.com69a99432012-01-10 18:00:10 +00002178 }
2179 }
reed@google.com69a99432012-01-10 18:00:10 +00002180
reed@google.comac8543f2012-01-30 20:51:25 +00002181 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2182}