blob: 362232b4e498190a177bd56574a3d003ac8b306e [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
1268 while ((verb = iter.next(pts)) != kDone_Verb) {
1269 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]) {
1393 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001394 // A special case: if both points are NaN, SkPoint::operation== returns
1395 // false, but the iterator expects that they are treated as the same.
1396 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001397 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1398 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001399 return kClose_Verb;
1400 }
1401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 if (pts) {
1403 pts[0] = fLastPt;
1404 pts[1] = fMoveTo;
1405 }
1406 fLastPt = fMoveTo;
1407 fCloseLine = true;
1408 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001409 } else {
1410 pts[0] = fMoveTo;
1411 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413}
1414
1415bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001416 if (fSegmentState == kAfterMove_SegmentState) {
1417 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 if (pts) {
1419 *pts = fMoveTo;
1420 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001421 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001423 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1424 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 if (pts) {
1426 *pts = fPts[-1];
1427 }
1428 }
1429 return false;
1430}
1431
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001432void SkPath::Iter::consumeDegenerateSegments() {
1433 // We need to step over anything that will not move the current draw point
1434 // forward before the next move is seen
1435 const uint8_t* lastMoveVerb = 0;
1436 const SkPoint* lastMovePt = 0;
1437 SkPoint lastPt = fLastPt;
1438 while (fVerbs != fVerbStop) {
1439 unsigned verb = *fVerbs;
1440 switch (verb) {
1441 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001442 // Keep a record of this most recent move
1443 lastMoveVerb = fVerbs;
1444 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001445 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001446 fVerbs++;
1447 fPts++;
1448 break;
1449
1450 case kClose_Verb:
1451 // A close when we are in a segment is always valid
1452 if (fSegmentState == kAfterPrimitive_SegmentState) {
1453 return;
1454 }
1455 // A close at any other time must be ignored
1456 fVerbs++;
1457 break;
1458
1459 case kLine_Verb:
1460 if (!IsLineDegenerate(lastPt, fPts[0])) {
1461 if (lastMoveVerb) {
1462 fVerbs = lastMoveVerb;
1463 fPts = lastMovePt;
1464 return;
1465 }
1466 return;
1467 }
1468 // Ignore this line and continue
1469 fVerbs++;
1470 fPts++;
1471 break;
1472
1473 case kQuad_Verb:
1474 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1475 if (lastMoveVerb) {
1476 fVerbs = lastMoveVerb;
1477 fPts = lastMovePt;
1478 return;
1479 }
1480 return;
1481 }
1482 // Ignore this line and continue
1483 fVerbs++;
1484 fPts += 2;
1485 break;
1486
1487 case kCubic_Verb:
1488 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1489 if (lastMoveVerb) {
1490 fVerbs = lastMoveVerb;
1491 fPts = lastMovePt;
1492 return;
1493 }
1494 return;
1495 }
1496 // Ignore this line and continue
1497 fVerbs++;
1498 fPts += 3;
1499 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001500
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001501 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001502 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001503 }
1504 }
1505}
1506
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001508 this->consumeDegenerateSegments();
1509
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001511 // Close the curve if requested and if there is some curve to close
1512 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 if (kLine_Verb == this->autoClose(pts)) {
1514 return kLine_Verb;
1515 }
1516 fNeedClose = false;
1517 return kClose_Verb;
1518 }
1519 return kDone_Verb;
1520 }
1521
1522 unsigned verb = *fVerbs++;
1523 const SkPoint* srcPts = fPts;
1524
1525 switch (verb) {
1526 case kMove_Verb:
1527 if (fNeedClose) {
1528 fVerbs -= 1;
1529 verb = this->autoClose(pts);
1530 if (verb == kClose_Verb) {
1531 fNeedClose = false;
1532 }
1533 return (Verb)verb;
1534 }
1535 if (fVerbs == fVerbStop) { // might be a trailing moveto
1536 return kDone_Verb;
1537 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001538 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 if (pts) {
1540 pts[0] = *srcPts;
1541 }
1542 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001543 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001544 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 fNeedClose = fForceClose;
1546 break;
1547 case kLine_Verb:
1548 if (this->cons_moveTo(pts)) {
1549 return kMove_Verb;
1550 }
1551 if (pts) {
1552 pts[1] = srcPts[0];
1553 }
1554 fLastPt = srcPts[0];
1555 fCloseLine = false;
1556 srcPts += 1;
1557 break;
1558 case kQuad_Verb:
1559 if (this->cons_moveTo(pts)) {
1560 return kMove_Verb;
1561 }
1562 if (pts) {
1563 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1564 }
1565 fLastPt = srcPts[1];
1566 srcPts += 2;
1567 break;
1568 case kCubic_Verb:
1569 if (this->cons_moveTo(pts)) {
1570 return kMove_Verb;
1571 }
1572 if (pts) {
1573 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1574 }
1575 fLastPt = srcPts[2];
1576 srcPts += 3;
1577 break;
1578 case kClose_Verb:
1579 verb = this->autoClose(pts);
1580 if (verb == kLine_Verb) {
1581 fVerbs -= 1;
1582 } else {
1583 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001584 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001586 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 break;
1588 }
1589 fPts = srcPts;
1590 return (Verb)verb;
1591}
1592
1593///////////////////////////////////////////////////////////////////////////////
1594
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001595SkPath::RawIter::RawIter() {
1596#ifdef SK_DEBUG
1597 fPts = NULL;
1598 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1599#endif
1600 // need to init enough to make next() harmlessly return kDone_Verb
1601 fVerbs = NULL;
1602 fVerbStop = NULL;
1603}
1604
1605SkPath::RawIter::RawIter(const SkPath& path) {
1606 this->setPath(path);
1607}
1608
1609void SkPath::RawIter::setPath(const SkPath& path) {
1610 fPts = path.fPts.begin();
1611 fVerbs = path.fVerbs.begin();
1612 fVerbStop = path.fVerbs.end();
1613 fMoveTo.fX = fMoveTo.fY = 0;
1614 fLastPt.fX = fLastPt.fY = 0;
1615}
1616
1617SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1618 if (fVerbs == fVerbStop) {
1619 return kDone_Verb;
1620 }
1621
1622 unsigned verb = *fVerbs++;
1623 const SkPoint* srcPts = fPts;
1624
1625 switch (verb) {
1626 case kMove_Verb:
1627 if (pts) {
1628 pts[0] = *srcPts;
1629 }
1630 fMoveTo = srcPts[0];
1631 fLastPt = fMoveTo;
1632 srcPts += 1;
1633 break;
1634 case kLine_Verb:
1635 if (pts) {
1636 pts[0] = fLastPt;
1637 pts[1] = srcPts[0];
1638 }
1639 fLastPt = srcPts[0];
1640 srcPts += 1;
1641 break;
1642 case kQuad_Verb:
1643 if (pts) {
1644 pts[0] = fLastPt;
1645 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1646 }
1647 fLastPt = srcPts[1];
1648 srcPts += 2;
1649 break;
1650 case kCubic_Verb:
1651 if (pts) {
1652 pts[0] = fLastPt;
1653 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1654 }
1655 fLastPt = srcPts[2];
1656 srcPts += 3;
1657 break;
1658 case kClose_Verb:
1659 fLastPt = fMoveTo;
1660 if (pts) {
1661 pts[0] = fMoveTo;
1662 }
1663 break;
1664 }
1665 fPts = srcPts;
1666 return (Verb)verb;
1667}
1668
1669///////////////////////////////////////////////////////////////////////////////
1670
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671/*
1672 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1673*/
1674
reed@google.com73945652011-04-25 19:04:27 +00001675void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 SkDEBUGCODE(this->validate();)
1677
1678 buffer.write32(fPts.count());
1679 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001680 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1682 buffer.writePad(fVerbs.begin(), fVerbs.count());
1683}
1684
reed@google.com73945652011-04-25 19:04:27 +00001685void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 fPts.setCount(buffer.readS32());
1687 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001688 uint32_t packed = buffer.readS32();
1689 fFillType = packed >> 8;
1690 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1692 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001693
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001694 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001695 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696
1697 SkDEBUGCODE(this->validate();)
1698}
1699
1700///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702void SkPath::dump(bool forceClose, const char title[]) const {
1703 Iter iter(*this, forceClose);
1704 SkPoint pts[4];
1705 Verb verb;
1706
1707 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1708 title ? title : "");
1709
1710 while ((verb = iter.next(pts)) != kDone_Verb) {
1711 switch (verb) {
1712 case kMove_Verb:
1713#ifdef SK_CAN_USE_FLOAT
1714 SkDebugf(" path: moveTo [%g %g]\n",
1715 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1716#else
1717 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1718#endif
1719 break;
1720 case kLine_Verb:
1721#ifdef SK_CAN_USE_FLOAT
1722 SkDebugf(" path: lineTo [%g %g]\n",
1723 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1724#else
1725 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1726#endif
1727 break;
1728 case kQuad_Verb:
1729#ifdef SK_CAN_USE_FLOAT
1730 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1731 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1732 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1733#else
1734 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1735 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1736#endif
1737 break;
1738 case kCubic_Verb:
1739#ifdef SK_CAN_USE_FLOAT
1740 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1741 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1742 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1743 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1744#else
1745 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1746 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1747 pts[3].fX, pts[3].fY);
1748#endif
1749 break;
1750 case kClose_Verb:
1751 SkDebugf(" path: close\n");
1752 break;
1753 default:
1754 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1755 verb = kDone_Verb; // stop the loop
1756 break;
1757 }
1758 }
1759 SkDebugf("path: done %s\n", title ? title : "");
1760}
1761
reed@android.come522ca52009-11-23 20:10:41 +00001762void SkPath::dump() const {
1763 this->dump(false);
1764}
1765
1766#ifdef SK_DEBUG
1767void SkPath::validate() const {
1768 SkASSERT(this != NULL);
1769 SkASSERT((fFillType & ~3) == 0);
1770 fPts.validate();
1771 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001772
reed@android.come522ca52009-11-23 20:10:41 +00001773 if (!fBoundsIsDirty) {
1774 SkRect bounds;
1775 compute_pt_bounds(&bounds, fPts);
1776 if (fPts.count() <= 1) {
1777 // if we're empty, fBounds may be empty but translated, so we can't
1778 // necessarily compare to bounds directly
1779 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1780 // be [2, 2, 2, 2]
1781 SkASSERT(bounds.isEmpty());
1782 SkASSERT(fBounds.isEmpty());
1783 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001784 if (bounds.isEmpty()) {
1785 SkASSERT(fBounds.isEmpty());
1786 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001787 if (!fBounds.isEmpty()) {
1788 SkASSERT(fBounds.contains(bounds));
1789 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001790 }
reed@android.come522ca52009-11-23 20:10:41 +00001791 }
1792 }
reed@google.com10296cc2011-09-21 12:29:05 +00001793
1794 uint32_t mask = 0;
1795 for (int i = 0; i < fVerbs.count(); i++) {
1796 switch (fVerbs[i]) {
1797 case kLine_Verb:
1798 mask |= kLine_SegmentMask;
1799 break;
1800 case kQuad_Verb:
1801 mask |= kQuad_SegmentMask;
1802 break;
1803 case kCubic_Verb:
1804 mask |= kCubic_SegmentMask;
1805 }
1806 }
1807 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001808}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809#endif
reed@android.come522ca52009-11-23 20:10:41 +00001810
reed@google.com04863fa2011-05-15 04:08:24 +00001811///////////////////////////////////////////////////////////////////////////////
1812
reed@google.com0b7b9822011-05-16 12:29:27 +00001813static int sign(SkScalar x) { return x < 0; }
1814#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001815
reed@google.com04863fa2011-05-15 04:08:24 +00001816static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001817 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001818}
1819
1820// only valid for a single contour
1821struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001822 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001823 fSign = 0;
1824 // warnings
1825 fCurrPt.set(0, 0);
1826 fVec0.set(0, 0);
1827 fVec1.set(0, 0);
1828 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001829
1830 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001831 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001832 }
1833
1834 SkPath::Convexity getConvexity() const { return fConvexity; }
1835
1836 void addPt(const SkPoint& pt) {
1837 if (SkPath::kConcave_Convexity == fConvexity) {
1838 return;
1839 }
1840
1841 if (0 == fPtCount) {
1842 fCurrPt = pt;
1843 ++fPtCount;
1844 } else {
1845 SkVector vec = pt - fCurrPt;
1846 if (vec.fX || vec.fY) {
1847 fCurrPt = pt;
1848 if (++fPtCount == 2) {
1849 fFirstVec = fVec1 = vec;
1850 } else {
1851 SkASSERT(fPtCount > 2);
1852 this->addVec(vec);
1853 }
reed@google.com85b6e392011-05-15 20:25:17 +00001854
1855 int sx = sign(vec.fX);
1856 int sy = sign(vec.fY);
1857 fDx += (sx != fSx);
1858 fDy += (sy != fSy);
1859 fSx = sx;
1860 fSy = sy;
1861
1862 if (fDx > 3 || fDy > 3) {
1863 fConvexity = SkPath::kConcave_Convexity;
1864 }
reed@google.com04863fa2011-05-15 04:08:24 +00001865 }
1866 }
1867 }
1868
1869 void close() {
1870 if (fPtCount > 2) {
1871 this->addVec(fFirstVec);
1872 }
1873 }
1874
1875private:
1876 void addVec(const SkVector& vec) {
1877 SkASSERT(vec.fX || vec.fY);
1878 fVec0 = fVec1;
1879 fVec1 = vec;
1880 int sign = CrossProductSign(fVec0, fVec1);
1881 if (0 == fSign) {
1882 fSign = sign;
1883 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001884 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001885 fConvexity = SkPath::kConcave_Convexity;
1886 }
1887 }
1888 }
1889
1890 SkPoint fCurrPt;
1891 SkVector fVec0, fVec1, fFirstVec;
1892 int fPtCount; // non-degenerate points
1893 int fSign;
1894 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001895 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001896};
1897
1898SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1899 SkPoint pts[4];
1900 SkPath::Verb verb;
1901 SkPath::Iter iter(path, true);
1902
1903 int contourCount = 0;
1904 int count;
1905 Convexicator state;
1906
1907 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1908 switch (verb) {
1909 case kMove_Verb:
1910 if (++contourCount > 1) {
1911 return kConcave_Convexity;
1912 }
1913 pts[1] = pts[0];
1914 count = 1;
1915 break;
1916 case kLine_Verb: count = 1; break;
1917 case kQuad_Verb: count = 2; break;
1918 case kCubic_Verb: count = 3; break;
1919 case kClose_Verb:
1920 state.close();
1921 count = 0;
1922 break;
1923 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001924 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001925 return kConcave_Convexity;
1926 }
1927
1928 for (int i = 1; i <= count; i++) {
1929 state.addPt(pts[i]);
1930 }
1931 // early exit
1932 if (kConcave_Convexity == state.getConvexity()) {
1933 return kConcave_Convexity;
1934 }
1935 }
1936 return state.getConvexity();
1937}
reed@google.com69a99432012-01-10 18:00:10 +00001938
1939///////////////////////////////////////////////////////////////////////////////
1940
1941class ContourIter {
1942public:
1943 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1944
1945 bool done() const { return fDone; }
1946 // if !done() then these may be called
1947 int count() const { return fCurrPtCount; }
1948 const SkPoint* pts() const { return fCurrPt; }
1949 void next();
1950
1951private:
1952 int fCurrPtCount;
1953 const SkPoint* fCurrPt;
1954 const uint8_t* fCurrVerb;
1955 const uint8_t* fStopVerbs;
1956 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001957 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001958};
1959
1960ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1961 const SkTDArray<SkPoint>& pts) {
1962 fStopVerbs = verbs.begin() + verbs.count();
1963
1964 fDone = false;
1965 fCurrPt = pts.begin();
1966 fCurrVerb = verbs.begin();
1967 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001968 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001969 this->next();
1970}
1971
1972void ContourIter::next() {
1973 if (fCurrVerb >= fStopVerbs) {
1974 fDone = true;
1975 }
1976 if (fDone) {
1977 return;
1978 }
1979
1980 // skip pts of prev contour
1981 fCurrPt += fCurrPtCount;
1982
1983 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1984 int ptCount = 1; // moveTo
1985 const uint8_t* verbs = fCurrVerb;
1986
1987 for (++verbs; verbs < fStopVerbs; ++verbs) {
1988 switch (*verbs) {
1989 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001990 goto CONTOUR_END;
1991 case SkPath::kLine_Verb:
1992 ptCount += 1;
1993 break;
1994 case SkPath::kQuad_Verb:
1995 ptCount += 2;
1996 break;
1997 case SkPath::kCubic_Verb:
1998 ptCount += 3;
1999 break;
2000 default: // kClose_Verb, just keep going
2001 break;
2002 }
2003 }
2004CONTOUR_END:
2005 fCurrPtCount = ptCount;
2006 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002007 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002008}
2009
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002010// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002011static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002012 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2013 // We may get 0 when the above subtracts underflow. We expect this to be
2014 // very rare and lazily promote to double.
2015 if (0 == cross) {
2016 double p0x = SkScalarToDouble(p0.fX);
2017 double p0y = SkScalarToDouble(p0.fY);
2018
2019 double p1x = SkScalarToDouble(p1.fX);
2020 double p1y = SkScalarToDouble(p1.fY);
2021
2022 double p2x = SkScalarToDouble(p2.fX);
2023 double p2y = SkScalarToDouble(p2.fY);
2024
2025 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2026 (p1y - p0y) * (p2x - p0x));
2027
2028 }
2029 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002030}
2031
reed@google.comc1ea60a2012-01-31 15:15:36 +00002032// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002033static int find_max_y(const SkPoint pts[], int count) {
2034 SkASSERT(count > 0);
2035 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002036 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002037 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002038 SkScalar y = pts[i].fY;
2039 if (y > max) {
2040 max = y;
2041 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002042 }
2043 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002044 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002045}
2046
reed@google.comcabaf1d2012-01-11 21:03:05 +00002047static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2048 int i = index;
2049 for (;;) {
2050 i = (i + inc) % n;
2051 if (i == index) { // we wrapped around, so abort
2052 break;
2053 }
2054 if (pts[index] != pts[i]) { // found a different point, success!
2055 break;
2056 }
2057 }
2058 return i;
2059}
2060
reed@google.comc1ea60a2012-01-31 15:15:36 +00002061/**
2062 * Starting at index, and moving forward (incrementing), find the xmin and
2063 * xmax of the contiguous points that have the same Y.
2064 */
2065static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2066 int* maxIndexPtr) {
2067 const SkScalar y = pts[index].fY;
2068 SkScalar min = pts[index].fX;
2069 SkScalar max = min;
2070 int minIndex = index;
2071 int maxIndex = index;
2072 for (int i = index + 1; i < n; ++i) {
2073 if (pts[i].fY != y) {
2074 break;
2075 }
2076 SkScalar x = pts[i].fX;
2077 if (x < min) {
2078 min = x;
2079 minIndex = i;
2080 } else if (x > max) {
2081 max = x;
2082 maxIndex = i;
2083 }
2084 }
2085 *maxIndexPtr = maxIndex;
2086 return minIndex;
2087}
2088
reed@google.comac8543f2012-01-30 20:51:25 +00002089static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2090 if (dir) {
2091 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2092 }
2093 return true;
2094}
2095
reed@google.comc1ea60a2012-01-31 15:15:36 +00002096#if 0
2097#include "SkString.h"
2098#include "../utils/SkParsePath.h"
2099static void dumpPath(const SkPath& path) {
2100 SkString str;
2101 SkParsePath::ToSVGString(path, &str);
2102 SkDebugf("%s\n", str.c_str());
2103}
2104#endif
2105
reed@google.comac8543f2012-01-30 20:51:25 +00002106/*
2107 * We loop through all contours, and keep the computed cross-product of the
2108 * contour that contained the global y-max. If we just look at the first
2109 * contour, we may find one that is wound the opposite way (correctly) since
2110 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2111 * that is outer most (or at least has the global y-max) before we can consider
2112 * its cross product.
2113 */
reed@google.com69a99432012-01-10 18:00:10 +00002114bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002115// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002116 // don't want to pay the cost for computing this if it
2117 // is unknown, so we don't call isConvex()
2118 const Convexity conv = this->getConvexityOrUnknown();
2119
2120 ContourIter iter(fVerbs, fPts);
2121
reed@google.comac8543f2012-01-30 20:51:25 +00002122 // initialize with our logical y-min
2123 SkScalar ymax = this->getBounds().fTop;
2124 SkScalar ymaxCross = 0;
2125
reed@google.com69a99432012-01-10 18:00:10 +00002126 for (; !iter.done(); iter.next()) {
2127 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002128 if (n < 3) {
2129 continue;
2130 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002131
reed@google.comcabaf1d2012-01-11 21:03:05 +00002132 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002133 SkScalar cross = 0;
2134 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002135 // we loop, skipping over degenerate or flat segments that will
2136 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002137 for (int i = 0; i < n - 2; ++i) {
2138 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2139 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002140 // early-exit, as kConvex is assumed to have only 1
2141 // non-degenerate contour
2142 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002143 }
2144 }
2145 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002146 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002147 if (pts[index].fY < ymax) {
2148 continue;
2149 }
2150
reed@google.comc1ea60a2012-01-31 15:15:36 +00002151 // If there is more than 1 distinct point at the y-max, we take the
2152 // x-min and x-max of them and just subtract to compute the dir.
2153 if (pts[(index + 1) % n].fY == pts[index].fY) {
2154 int maxIndex;
2155 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002156 if (minIndex == maxIndex) {
2157 goto TRY_CROSSPROD;
2158 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002159 SkASSERT(pts[minIndex].fY == pts[index].fY);
2160 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2161 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2162 // we just subtract the indices, and let that auto-convert to
2163 // SkScalar, since we just want - or + to signal the direction.
2164 cross = minIndex - maxIndex;
2165 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002166 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002167 // Find a next and prev index to use for the cross-product test,
2168 // but we try to find pts that form non-zero vectors from pts[index]
2169 //
2170 // Its possible that we can't find two non-degenerate vectors, so
2171 // we have to guard our search (e.g. all the pts could be in the
2172 // same place).
2173
2174 // we pass n - 1 instead of -1 so we don't foul up % operator by
2175 // passing it a negative LH argument.
2176 int prev = find_diff_pt(pts, index, n, n - 1);
2177 if (prev == index) {
2178 // completely degenerate, skip to next contour
2179 continue;
2180 }
2181 int next = find_diff_pt(pts, index, n, 1);
2182 SkASSERT(next != index);
2183 cross = cross_prod(pts[prev], pts[index], pts[next]);
2184 // if we get a zero, but the pts aren't on top of each other, then
2185 // we can just look at the direction
2186 if (0 == cross) {
2187 // construct the subtract so we get the correct Direction below
2188 cross = pts[index].fX - pts[next].fX;
2189 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002190 }
reed@google.comac8543f2012-01-30 20:51:25 +00002191
2192 if (cross) {
2193 // record our best guess so far
2194 ymax = pts[index].fY;
2195 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002196 }
reed@google.com69a99432012-01-10 18:00:10 +00002197 }
2198 }
reed@google.com69a99432012-01-10 18:00:10 +00002199
reed@google.comac8543f2012-01-30 20:51:25 +00002200 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2201}