blob: 1a488308c7e98028975b351c1652cf9e0c305b17 [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]) {
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.com9e25dbf2012-05-15 17:05:38 +00001501SkPath::Verb SkPath::Iter::next(SkPoint ptsParam[4]) {
1502 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001503 this->consumeDegenerateSegments();
1504
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001506 // Close the curve if requested and if there is some curve to close
1507 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001508 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 return kLine_Verb;
1510 }
1511 fNeedClose = false;
1512 return kClose_Verb;
1513 }
1514 return kDone_Verb;
1515 }
1516
1517 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001518 const SkPoint* SK_RESTRICT srcPts = fPts;
1519 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520
1521 switch (verb) {
1522 case kMove_Verb:
1523 if (fNeedClose) {
1524 fVerbs -= 1;
1525 verb = this->autoClose(pts);
1526 if (verb == kClose_Verb) {
1527 fNeedClose = false;
1528 }
1529 return (Verb)verb;
1530 }
1531 if (fVerbs == fVerbStop) { // might be a trailing moveto
1532 return kDone_Verb;
1533 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001534 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001535 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001537 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001538 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 fNeedClose = fForceClose;
1540 break;
1541 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001542 pts[0] = this->cons_moveTo();
1543 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 fLastPt = srcPts[0];
1545 fCloseLine = false;
1546 srcPts += 1;
1547 break;
1548 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001549 pts[0] = this->cons_moveTo();
1550 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 fLastPt = srcPts[1];
1552 srcPts += 2;
1553 break;
1554 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001555 pts[0] = this->cons_moveTo();
1556 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 fLastPt = srcPts[2];
1558 srcPts += 3;
1559 break;
1560 case kClose_Verb:
1561 verb = this->autoClose(pts);
1562 if (verb == kLine_Verb) {
1563 fVerbs -= 1;
1564 } else {
1565 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001566 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001568 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 break;
1570 }
1571 fPts = srcPts;
1572 return (Verb)verb;
1573}
1574
1575///////////////////////////////////////////////////////////////////////////////
1576
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001577SkPath::RawIter::RawIter() {
1578#ifdef SK_DEBUG
1579 fPts = NULL;
1580 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1581#endif
1582 // need to init enough to make next() harmlessly return kDone_Verb
1583 fVerbs = NULL;
1584 fVerbStop = NULL;
1585}
1586
1587SkPath::RawIter::RawIter(const SkPath& path) {
1588 this->setPath(path);
1589}
1590
1591void SkPath::RawIter::setPath(const SkPath& path) {
1592 fPts = path.fPts.begin();
1593 fVerbs = path.fVerbs.begin();
1594 fVerbStop = path.fVerbs.end();
1595 fMoveTo.fX = fMoveTo.fY = 0;
1596 fLastPt.fX = fLastPt.fY = 0;
1597}
1598
1599SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1600 if (fVerbs == fVerbStop) {
1601 return kDone_Verb;
1602 }
1603
1604 unsigned verb = *fVerbs++;
1605 const SkPoint* srcPts = fPts;
1606
1607 switch (verb) {
1608 case kMove_Verb:
1609 if (pts) {
1610 pts[0] = *srcPts;
1611 }
1612 fMoveTo = srcPts[0];
1613 fLastPt = fMoveTo;
1614 srcPts += 1;
1615 break;
1616 case kLine_Verb:
1617 if (pts) {
1618 pts[0] = fLastPt;
1619 pts[1] = srcPts[0];
1620 }
1621 fLastPt = srcPts[0];
1622 srcPts += 1;
1623 break;
1624 case kQuad_Verb:
1625 if (pts) {
1626 pts[0] = fLastPt;
1627 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1628 }
1629 fLastPt = srcPts[1];
1630 srcPts += 2;
1631 break;
1632 case kCubic_Verb:
1633 if (pts) {
1634 pts[0] = fLastPt;
1635 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1636 }
1637 fLastPt = srcPts[2];
1638 srcPts += 3;
1639 break;
1640 case kClose_Verb:
1641 fLastPt = fMoveTo;
1642 if (pts) {
1643 pts[0] = fMoveTo;
1644 }
1645 break;
1646 }
1647 fPts = srcPts;
1648 return (Verb)verb;
1649}
1650
1651///////////////////////////////////////////////////////////////////////////////
1652
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653/*
1654 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1655*/
1656
reed@google.com73945652011-04-25 19:04:27 +00001657void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 SkDEBUGCODE(this->validate();)
1659
1660 buffer.write32(fPts.count());
1661 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001662 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1664 buffer.writePad(fVerbs.begin(), fVerbs.count());
1665}
1666
reed@google.com73945652011-04-25 19:04:27 +00001667void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 fPts.setCount(buffer.readS32());
1669 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001670 uint32_t packed = buffer.readS32();
1671 fFillType = packed >> 8;
1672 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1674 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001675
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001676 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001677 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678
1679 SkDEBUGCODE(this->validate();)
1680}
1681
1682///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684void SkPath::dump(bool forceClose, const char title[]) const {
1685 Iter iter(*this, forceClose);
1686 SkPoint pts[4];
1687 Verb verb;
1688
1689 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1690 title ? title : "");
1691
1692 while ((verb = iter.next(pts)) != kDone_Verb) {
1693 switch (verb) {
1694 case kMove_Verb:
1695#ifdef SK_CAN_USE_FLOAT
1696 SkDebugf(" path: moveTo [%g %g]\n",
1697 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1698#else
1699 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1700#endif
1701 break;
1702 case kLine_Verb:
1703#ifdef SK_CAN_USE_FLOAT
1704 SkDebugf(" path: lineTo [%g %g]\n",
1705 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1706#else
1707 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1708#endif
1709 break;
1710 case kQuad_Verb:
1711#ifdef SK_CAN_USE_FLOAT
1712 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1713 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1714 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1715#else
1716 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1717 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1718#endif
1719 break;
1720 case kCubic_Verb:
1721#ifdef SK_CAN_USE_FLOAT
1722 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1723 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1724 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1725 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1726#else
1727 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1728 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1729 pts[3].fX, pts[3].fY);
1730#endif
1731 break;
1732 case kClose_Verb:
1733 SkDebugf(" path: close\n");
1734 break;
1735 default:
1736 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1737 verb = kDone_Verb; // stop the loop
1738 break;
1739 }
1740 }
1741 SkDebugf("path: done %s\n", title ? title : "");
1742}
1743
reed@android.come522ca52009-11-23 20:10:41 +00001744void SkPath::dump() const {
1745 this->dump(false);
1746}
1747
1748#ifdef SK_DEBUG
1749void SkPath::validate() const {
1750 SkASSERT(this != NULL);
1751 SkASSERT((fFillType & ~3) == 0);
1752 fPts.validate();
1753 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001754
reed@android.come522ca52009-11-23 20:10:41 +00001755 if (!fBoundsIsDirty) {
1756 SkRect bounds;
1757 compute_pt_bounds(&bounds, fPts);
1758 if (fPts.count() <= 1) {
1759 // if we're empty, fBounds may be empty but translated, so we can't
1760 // necessarily compare to bounds directly
1761 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1762 // be [2, 2, 2, 2]
1763 SkASSERT(bounds.isEmpty());
1764 SkASSERT(fBounds.isEmpty());
1765 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001766 if (bounds.isEmpty()) {
1767 SkASSERT(fBounds.isEmpty());
1768 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001769 if (!fBounds.isEmpty()) {
1770 SkASSERT(fBounds.contains(bounds));
1771 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001772 }
reed@android.come522ca52009-11-23 20:10:41 +00001773 }
1774 }
reed@google.com10296cc2011-09-21 12:29:05 +00001775
1776 uint32_t mask = 0;
1777 for (int i = 0; i < fVerbs.count(); i++) {
1778 switch (fVerbs[i]) {
1779 case kLine_Verb:
1780 mask |= kLine_SegmentMask;
1781 break;
1782 case kQuad_Verb:
1783 mask |= kQuad_SegmentMask;
1784 break;
1785 case kCubic_Verb:
1786 mask |= kCubic_SegmentMask;
1787 }
1788 }
1789 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001790}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791#endif
reed@android.come522ca52009-11-23 20:10:41 +00001792
reed@google.com04863fa2011-05-15 04:08:24 +00001793///////////////////////////////////////////////////////////////////////////////
1794
reed@google.com0b7b9822011-05-16 12:29:27 +00001795static int sign(SkScalar x) { return x < 0; }
1796#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001797
reed@google.com04863fa2011-05-15 04:08:24 +00001798static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001799 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001800}
1801
1802// only valid for a single contour
1803struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001804 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001805 fSign = 0;
1806 // warnings
1807 fCurrPt.set(0, 0);
1808 fVec0.set(0, 0);
1809 fVec1.set(0, 0);
1810 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001811
1812 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001813 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001814 }
1815
1816 SkPath::Convexity getConvexity() const { return fConvexity; }
1817
1818 void addPt(const SkPoint& pt) {
1819 if (SkPath::kConcave_Convexity == fConvexity) {
1820 return;
1821 }
1822
1823 if (0 == fPtCount) {
1824 fCurrPt = pt;
1825 ++fPtCount;
1826 } else {
1827 SkVector vec = pt - fCurrPt;
1828 if (vec.fX || vec.fY) {
1829 fCurrPt = pt;
1830 if (++fPtCount == 2) {
1831 fFirstVec = fVec1 = vec;
1832 } else {
1833 SkASSERT(fPtCount > 2);
1834 this->addVec(vec);
1835 }
reed@google.com85b6e392011-05-15 20:25:17 +00001836
1837 int sx = sign(vec.fX);
1838 int sy = sign(vec.fY);
1839 fDx += (sx != fSx);
1840 fDy += (sy != fSy);
1841 fSx = sx;
1842 fSy = sy;
1843
1844 if (fDx > 3 || fDy > 3) {
1845 fConvexity = SkPath::kConcave_Convexity;
1846 }
reed@google.com04863fa2011-05-15 04:08:24 +00001847 }
1848 }
1849 }
1850
1851 void close() {
1852 if (fPtCount > 2) {
1853 this->addVec(fFirstVec);
1854 }
1855 }
1856
1857private:
1858 void addVec(const SkVector& vec) {
1859 SkASSERT(vec.fX || vec.fY);
1860 fVec0 = fVec1;
1861 fVec1 = vec;
1862 int sign = CrossProductSign(fVec0, fVec1);
1863 if (0 == fSign) {
1864 fSign = sign;
1865 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001866 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001867 fConvexity = SkPath::kConcave_Convexity;
1868 }
1869 }
1870 }
1871
1872 SkPoint fCurrPt;
1873 SkVector fVec0, fVec1, fFirstVec;
1874 int fPtCount; // non-degenerate points
1875 int fSign;
1876 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001877 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001878};
1879
1880SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1881 SkPoint pts[4];
1882 SkPath::Verb verb;
1883 SkPath::Iter iter(path, true);
1884
1885 int contourCount = 0;
1886 int count;
1887 Convexicator state;
1888
1889 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1890 switch (verb) {
1891 case kMove_Verb:
1892 if (++contourCount > 1) {
1893 return kConcave_Convexity;
1894 }
1895 pts[1] = pts[0];
1896 count = 1;
1897 break;
1898 case kLine_Verb: count = 1; break;
1899 case kQuad_Verb: count = 2; break;
1900 case kCubic_Verb: count = 3; break;
1901 case kClose_Verb:
1902 state.close();
1903 count = 0;
1904 break;
1905 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001906 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001907 return kConcave_Convexity;
1908 }
1909
1910 for (int i = 1; i <= count; i++) {
1911 state.addPt(pts[i]);
1912 }
1913 // early exit
1914 if (kConcave_Convexity == state.getConvexity()) {
1915 return kConcave_Convexity;
1916 }
1917 }
1918 return state.getConvexity();
1919}
reed@google.com69a99432012-01-10 18:00:10 +00001920
1921///////////////////////////////////////////////////////////////////////////////
1922
1923class ContourIter {
1924public:
1925 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1926
1927 bool done() const { return fDone; }
1928 // if !done() then these may be called
1929 int count() const { return fCurrPtCount; }
1930 const SkPoint* pts() const { return fCurrPt; }
1931 void next();
1932
1933private:
1934 int fCurrPtCount;
1935 const SkPoint* fCurrPt;
1936 const uint8_t* fCurrVerb;
1937 const uint8_t* fStopVerbs;
1938 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001939 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001940};
1941
1942ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1943 const SkTDArray<SkPoint>& pts) {
1944 fStopVerbs = verbs.begin() + verbs.count();
1945
1946 fDone = false;
1947 fCurrPt = pts.begin();
1948 fCurrVerb = verbs.begin();
1949 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001950 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001951 this->next();
1952}
1953
1954void ContourIter::next() {
1955 if (fCurrVerb >= fStopVerbs) {
1956 fDone = true;
1957 }
1958 if (fDone) {
1959 return;
1960 }
1961
1962 // skip pts of prev contour
1963 fCurrPt += fCurrPtCount;
1964
1965 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1966 int ptCount = 1; // moveTo
1967 const uint8_t* verbs = fCurrVerb;
1968
1969 for (++verbs; verbs < fStopVerbs; ++verbs) {
1970 switch (*verbs) {
1971 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001972 goto CONTOUR_END;
1973 case SkPath::kLine_Verb:
1974 ptCount += 1;
1975 break;
1976 case SkPath::kQuad_Verb:
1977 ptCount += 2;
1978 break;
1979 case SkPath::kCubic_Verb:
1980 ptCount += 3;
1981 break;
1982 default: // kClose_Verb, just keep going
1983 break;
1984 }
1985 }
1986CONTOUR_END:
1987 fCurrPtCount = ptCount;
1988 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001989 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001990}
1991
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001992// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00001993static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00001994 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
1995 // We may get 0 when the above subtracts underflow. We expect this to be
1996 // very rare and lazily promote to double.
1997 if (0 == cross) {
1998 double p0x = SkScalarToDouble(p0.fX);
1999 double p0y = SkScalarToDouble(p0.fY);
2000
2001 double p1x = SkScalarToDouble(p1.fX);
2002 double p1y = SkScalarToDouble(p1.fY);
2003
2004 double p2x = SkScalarToDouble(p2.fX);
2005 double p2y = SkScalarToDouble(p2.fY);
2006
2007 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2008 (p1y - p0y) * (p2x - p0x));
2009
2010 }
2011 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002012}
2013
reed@google.comc1ea60a2012-01-31 15:15:36 +00002014// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002015static int find_max_y(const SkPoint pts[], int count) {
2016 SkASSERT(count > 0);
2017 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002018 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002019 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002020 SkScalar y = pts[i].fY;
2021 if (y > max) {
2022 max = y;
2023 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002024 }
2025 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002026 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002027}
2028
reed@google.comcabaf1d2012-01-11 21:03:05 +00002029static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2030 int i = index;
2031 for (;;) {
2032 i = (i + inc) % n;
2033 if (i == index) { // we wrapped around, so abort
2034 break;
2035 }
2036 if (pts[index] != pts[i]) { // found a different point, success!
2037 break;
2038 }
2039 }
2040 return i;
2041}
2042
reed@google.comc1ea60a2012-01-31 15:15:36 +00002043/**
2044 * Starting at index, and moving forward (incrementing), find the xmin and
2045 * xmax of the contiguous points that have the same Y.
2046 */
2047static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2048 int* maxIndexPtr) {
2049 const SkScalar y = pts[index].fY;
2050 SkScalar min = pts[index].fX;
2051 SkScalar max = min;
2052 int minIndex = index;
2053 int maxIndex = index;
2054 for (int i = index + 1; i < n; ++i) {
2055 if (pts[i].fY != y) {
2056 break;
2057 }
2058 SkScalar x = pts[i].fX;
2059 if (x < min) {
2060 min = x;
2061 minIndex = i;
2062 } else if (x > max) {
2063 max = x;
2064 maxIndex = i;
2065 }
2066 }
2067 *maxIndexPtr = maxIndex;
2068 return minIndex;
2069}
2070
reed@google.comac8543f2012-01-30 20:51:25 +00002071static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2072 if (dir) {
2073 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2074 }
2075 return true;
2076}
2077
reed@google.comc1ea60a2012-01-31 15:15:36 +00002078#if 0
2079#include "SkString.h"
2080#include "../utils/SkParsePath.h"
2081static void dumpPath(const SkPath& path) {
2082 SkString str;
2083 SkParsePath::ToSVGString(path, &str);
2084 SkDebugf("%s\n", str.c_str());
2085}
2086#endif
2087
reed@google.comac8543f2012-01-30 20:51:25 +00002088/*
2089 * We loop through all contours, and keep the computed cross-product of the
2090 * contour that contained the global y-max. If we just look at the first
2091 * contour, we may find one that is wound the opposite way (correctly) since
2092 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2093 * that is outer most (or at least has the global y-max) before we can consider
2094 * its cross product.
2095 */
reed@google.com69a99432012-01-10 18:00:10 +00002096bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002097// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002098 // don't want to pay the cost for computing this if it
2099 // is unknown, so we don't call isConvex()
2100 const Convexity conv = this->getConvexityOrUnknown();
2101
2102 ContourIter iter(fVerbs, fPts);
2103
reed@google.comac8543f2012-01-30 20:51:25 +00002104 // initialize with our logical y-min
2105 SkScalar ymax = this->getBounds().fTop;
2106 SkScalar ymaxCross = 0;
2107
reed@google.com69a99432012-01-10 18:00:10 +00002108 for (; !iter.done(); iter.next()) {
2109 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002110 if (n < 3) {
2111 continue;
2112 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002113
reed@google.comcabaf1d2012-01-11 21:03:05 +00002114 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002115 SkScalar cross = 0;
2116 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002117 // we loop, skipping over degenerate or flat segments that will
2118 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002119 for (int i = 0; i < n - 2; ++i) {
2120 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2121 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002122 // early-exit, as kConvex is assumed to have only 1
2123 // non-degenerate contour
2124 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002125 }
2126 }
2127 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002128 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002129 if (pts[index].fY < ymax) {
2130 continue;
2131 }
2132
reed@google.comc1ea60a2012-01-31 15:15:36 +00002133 // If there is more than 1 distinct point at the y-max, we take the
2134 // x-min and x-max of them and just subtract to compute the dir.
2135 if (pts[(index + 1) % n].fY == pts[index].fY) {
2136 int maxIndex;
2137 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002138 if (minIndex == maxIndex) {
2139 goto TRY_CROSSPROD;
2140 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002141 SkASSERT(pts[minIndex].fY == pts[index].fY);
2142 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2143 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2144 // we just subtract the indices, and let that auto-convert to
2145 // SkScalar, since we just want - or + to signal the direction.
2146 cross = minIndex - maxIndex;
2147 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002148 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002149 // Find a next and prev index to use for the cross-product test,
2150 // but we try to find pts that form non-zero vectors from pts[index]
2151 //
2152 // Its possible that we can't find two non-degenerate vectors, so
2153 // we have to guard our search (e.g. all the pts could be in the
2154 // same place).
2155
2156 // we pass n - 1 instead of -1 so we don't foul up % operator by
2157 // passing it a negative LH argument.
2158 int prev = find_diff_pt(pts, index, n, n - 1);
2159 if (prev == index) {
2160 // completely degenerate, skip to next contour
2161 continue;
2162 }
2163 int next = find_diff_pt(pts, index, n, 1);
2164 SkASSERT(next != index);
2165 cross = cross_prod(pts[prev], pts[index], pts[next]);
2166 // if we get a zero, but the pts aren't on top of each other, then
2167 // we can just look at the direction
2168 if (0 == cross) {
2169 // construct the subtract so we get the correct Direction below
2170 cross = pts[index].fX - pts[next].fX;
2171 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002172 }
reed@google.comac8543f2012-01-30 20:51:25 +00002173
2174 if (cross) {
2175 // record our best guess so far
2176 ymax = pts[index].fY;
2177 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002178 }
reed@google.com69a99432012-01-10 18:00:10 +00002179 }
2180 }
reed@google.com69a99432012-01-10 18:00:10 +00002181
reed@google.comac8543f2012-01-30 20:51:25 +00002182 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2183}