blob: de34fe8e1e3aa0f05921ae5bd4e52da491a4055c [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
caryclark@google.comf1316942011-07-26 19:54:45 +0000252/*
253 Determines if path is a rect by keeping track of changes in direction
254 and looking for a loop either clockwise or counterclockwise.
255
256 The direction is computed such that:
257 0: vertical up
258 1: horizontal right
259 2: vertical down
260 3: horizontal left
261
262A rectangle cycles up/right/down/left or up/left/down/right.
263
264The test fails if:
265 The path is closed, and followed by a line.
266 A second move creates a new endpoint.
267 A diagonal line is parsed.
268 There's more than four changes of direction.
269 There's a discontinuity on the line (e.g., a move in the middle)
270 The line reverses direction.
271 The rectangle doesn't complete a cycle.
272 The path contains a quadratic or cubic.
273 The path contains fewer than four points.
274 The final point isn't equal to the first point.
275
276It's OK if the path has:
277 Several colinear line segments composing a rectangle side.
278 Single points on the rectangle side.
279
280The direction takes advantage of the corners found since opposite sides
281must travel in opposite directions.
282
283FIXME: Allow colinear quads and cubics to be treated like lines.
284FIXME: If the API passes fill-only, return true if the filled stroke
285 is a rectangle, though the caller failed to close the path.
286 */
287bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000289
caryclark@google.comf1316942011-07-26 19:54:45 +0000290 int corners = 0;
291 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000292 first.set(0, 0);
293 last.set(0, 0);
294 int firstDirection = 0;
295 int lastDirection = 0;
296 int nextDirection = 0;
297 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000298 bool autoClose = false;
299 const uint8_t* verbs = fVerbs.begin();
300 const uint8_t* verbStop = fVerbs.end();
301 const SkPoint* pts = fPts.begin();
302 while (verbs != verbStop) {
303 switch (*verbs++) {
304 case kClose_Verb:
305 pts = fPts.begin();
306 autoClose = true;
307 case kLine_Verb: {
308 SkScalar left = last.fX;
309 SkScalar top = last.fY;
310 SkScalar right = pts->fX;
311 SkScalar bottom = pts->fY;
312 ++pts;
313 if (left != right && top != bottom) {
314 return false; // diagonal
315 }
316 if (left == right && top == bottom) {
317 break; // single point on side OK
318 }
319 nextDirection = (left != right) << 0 |
320 (left < right || top < bottom) << 1;
321 if (0 == corners) {
322 firstDirection = nextDirection;
323 first = last;
324 last = pts[-1];
325 corners = 1;
326 closedOrMoved = false;
327 break;
328 }
329 if (closedOrMoved) {
330 return false; // closed followed by a line
331 }
332 closedOrMoved = autoClose;
333 if (lastDirection != nextDirection) {
334 if (++corners > 4) {
335 return false; // too many direction changes
336 }
337 }
338 last = pts[-1];
339 if (lastDirection == nextDirection) {
340 break; // colinear segment
341 }
342 // Possible values for corners are 2, 3, and 4.
343 // When corners == 3, nextDirection opposes firstDirection.
344 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000345 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000346 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
347 if ((directionCycle ^ turn) != nextDirection) {
348 return false; // direction didn't follow cycle
349 }
350 break;
351 }
352 case kQuad_Verb:
353 case kCubic_Verb:
354 return false; // quadratic, cubic not allowed
355 case kMove_Verb:
356 last = *pts++;
357 closedOrMoved = true;
358 break;
359 }
360 lastDirection = nextDirection;
361 }
362 // Success if 4 corners and first point equals last
363 bool result = 4 == corners && first == last;
364 if (result && rect) {
365 *rect = getBounds();
366 }
367 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368}
369
370int SkPath::getPoints(SkPoint copy[], int max) const {
371 SkDEBUGCODE(this->validate();)
372
373 SkASSERT(max >= 0);
374 int count = fPts.count();
375 if (copy && max > 0 && count > 0) {
376 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
377 }
378 return count;
379}
380
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000381SkPoint SkPath::getPoint(int index) const {
382 if ((unsigned)index < (unsigned)fPts.count()) {
383 return fPts[index];
384 }
385 return SkPoint::Make(0, 0);
386}
387
reed@google.com294dd7b2011-10-11 11:58:32 +0000388bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 SkDEBUGCODE(this->validate();)
390
reed@google.com294dd7b2011-10-11 11:58:32 +0000391 int count = fPts.count();
392 if (count > 0) {
393 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394 *lastPt = fPts[count - 1];
395 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000396 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000398 if (lastPt) {
399 lastPt->set(0, 0);
400 }
401 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402}
403
404void SkPath::setLastPt(SkScalar x, SkScalar y) {
405 SkDEBUGCODE(this->validate();)
406
407 int count = fPts.count();
408 if (count == 0) {
409 this->moveTo(x, y);
410 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000411 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000413 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000414 }
415}
416
reed@android.comd252db02009-04-01 18:31:44 +0000417void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000419 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420
reed@android.comd252db02009-04-01 18:31:44 +0000421 fBoundsIsDirty = false;
422 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423}
424
reed@google.com04863fa2011-05-15 04:08:24 +0000425void SkPath::setConvexity(Convexity c) {
426 if (fConvexity != c) {
427 fConvexity = c;
428 GEN_ID_INC;
429 }
430}
431
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432//////////////////////////////////////////////////////////////////////////////
433// Construction methods
434
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000435#define DIRTY_AFTER_EDIT \
436 do { \
437 fBoundsIsDirty = true; \
438 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000439 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000440 } while (0)
441
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000442#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
443 do { \
444 fBoundsIsDirty = true; \
445 } while (0)
446
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447void SkPath::incReserve(U16CPU inc) {
448 SkDEBUGCODE(this->validate();)
449
450 fVerbs.setReserve(fVerbs.count() + inc);
451 fPts.setReserve(fPts.count() + inc);
452
453 SkDEBUGCODE(this->validate();)
454}
455
456void SkPath::moveTo(SkScalar x, SkScalar y) {
457 SkDEBUGCODE(this->validate();)
458
459 int vc = fVerbs.count();
460 SkPoint* pt;
461
reed@google.comd335d1d2012-01-12 18:17:11 +0000462 // remember our index
463 fLastMoveToIndex = fPts.count();
464
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000465 pt = fPts.append();
466 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000467 pt->set(x, y);
468
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000469 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000470 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471}
472
473void SkPath::rMoveTo(SkScalar x, SkScalar y) {
474 SkPoint pt;
475 this->getLastPt(&pt);
476 this->moveTo(pt.fX + x, pt.fY + y);
477}
478
reed@google.comd335d1d2012-01-12 18:17:11 +0000479void SkPath::injectMoveToIfNeeded() {
480 if (fLastMoveToIndex < 0) {
481 SkScalar x, y;
482 if (fVerbs.count() == 0) {
483 x = y = 0;
484 } else {
485 const SkPoint& pt = fPts[~fLastMoveToIndex];
486 x = pt.fX;
487 y = pt.fY;
488 }
489 this->moveTo(x, y);
490 }
491}
492
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493void SkPath::lineTo(SkScalar x, SkScalar y) {
494 SkDEBUGCODE(this->validate();)
495
reed@google.comd335d1d2012-01-12 18:17:11 +0000496 this->injectMoveToIfNeeded();
497
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498 fPts.append()->set(x, y);
499 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000500 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000501
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000502 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000503 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000504}
505
506void SkPath::rLineTo(SkScalar x, SkScalar y) {
507 SkPoint pt;
508 this->getLastPt(&pt);
509 this->lineTo(pt.fX + x, pt.fY + y);
510}
511
512void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
513 SkDEBUGCODE(this->validate();)
514
reed@google.comd335d1d2012-01-12 18:17:11 +0000515 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516
517 SkPoint* pts = fPts.append(2);
518 pts[0].set(x1, y1);
519 pts[1].set(x2, y2);
520 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000521 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000523 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000524 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525}
526
527void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
528 SkPoint pt;
529 this->getLastPt(&pt);
530 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
531}
532
533void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
534 SkScalar x3, SkScalar y3) {
535 SkDEBUGCODE(this->validate();)
536
reed@google.comd335d1d2012-01-12 18:17:11 +0000537 this->injectMoveToIfNeeded();
538
reed@android.com8a1c16f2008-12-17 15:59:43 +0000539 SkPoint* pts = fPts.append(3);
540 pts[0].set(x1, y1);
541 pts[1].set(x2, y2);
542 pts[2].set(x3, y3);
543 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000544 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000546 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000547 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000548}
549
550void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
551 SkScalar x3, SkScalar y3) {
552 SkPoint pt;
553 this->getLastPt(&pt);
554 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
555 pt.fX + x3, pt.fY + y3);
556}
557
558void SkPath::close() {
559 SkDEBUGCODE(this->validate();)
560
561 int count = fVerbs.count();
562 if (count > 0) {
563 switch (fVerbs[count - 1]) {
564 case kLine_Verb:
565 case kQuad_Verb:
566 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000567 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000568 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000569 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 break;
571 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000572 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 break;
574 }
575 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000576
577 // signal that we need a moveTo to follow us (unless we're done)
578#if 0
579 if (fLastMoveToIndex >= 0) {
580 fLastMoveToIndex = ~fLastMoveToIndex;
581 }
582#else
583 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
584#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585}
586
587///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000588
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589void SkPath::addRect(const SkRect& rect, Direction dir) {
590 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
591}
592
593void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
594 SkScalar bottom, Direction dir) {
595 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
596
597 this->incReserve(5);
598
599 this->moveTo(left, top);
600 if (dir == kCCW_Direction) {
601 this->lineTo(left, bottom);
602 this->lineTo(right, bottom);
603 this->lineTo(right, top);
604 } else {
605 this->lineTo(right, top);
606 this->lineTo(right, bottom);
607 this->lineTo(left, bottom);
608 }
609 this->close();
610}
611
612#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
613
614void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
615 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 SkScalar w = rect.width();
617 SkScalar halfW = SkScalarHalf(w);
618 SkScalar h = rect.height();
619 SkScalar halfH = SkScalarHalf(h);
620
621 if (halfW <= 0 || halfH <= 0) {
622 return;
623 }
624
reed@google.comabf15c12011-01-18 20:35:51 +0000625 bool skip_hori = rx >= halfW;
626 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627
628 if (skip_hori && skip_vert) {
629 this->addOval(rect, dir);
630 return;
631 }
reed@google.comabf15c12011-01-18 20:35:51 +0000632
633 SkAutoPathBoundsUpdate apbu(this, rect);
634
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 if (skip_hori) {
636 rx = halfW;
637 } else if (skip_vert) {
638 ry = halfH;
639 }
640
641 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
642 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
643
644 this->incReserve(17);
645 this->moveTo(rect.fRight - rx, rect.fTop);
646 if (dir == kCCW_Direction) {
647 if (!skip_hori) {
648 this->lineTo(rect.fLeft + rx, rect.fTop); // top
649 }
650 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
651 rect.fLeft, rect.fTop + ry - sy,
652 rect.fLeft, rect.fTop + ry); // top-left
653 if (!skip_vert) {
654 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
655 }
656 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
657 rect.fLeft + rx - sx, rect.fBottom,
658 rect.fLeft + rx, rect.fBottom); // bot-left
659 if (!skip_hori) {
660 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
661 }
662 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
663 rect.fRight, rect.fBottom - ry + sy,
664 rect.fRight, rect.fBottom - ry); // bot-right
665 if (!skip_vert) {
666 this->lineTo(rect.fRight, rect.fTop + ry);
667 }
668 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
669 rect.fRight - rx + sx, rect.fTop,
670 rect.fRight - rx, rect.fTop); // top-right
671 } else {
672 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
673 rect.fRight, rect.fTop + ry - sy,
674 rect.fRight, rect.fTop + ry); // top-right
675 if (!skip_vert) {
676 this->lineTo(rect.fRight, rect.fBottom - ry);
677 }
678 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
679 rect.fRight - rx + sx, rect.fBottom,
680 rect.fRight - rx, rect.fBottom); // bot-right
681 if (!skip_hori) {
682 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
683 }
684 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
685 rect.fLeft, rect.fBottom - ry + sy,
686 rect.fLeft, rect.fBottom - ry); // bot-left
687 if (!skip_vert) {
688 this->lineTo(rect.fLeft, rect.fTop + ry); // left
689 }
690 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
691 rect.fLeft + rx - sx, rect.fTop,
692 rect.fLeft + rx, rect.fTop); // top-left
693 if (!skip_hori) {
694 this->lineTo(rect.fRight - rx, rect.fTop); // top
695 }
696 }
697 this->close();
698}
699
700static void add_corner_arc(SkPath* path, const SkRect& rect,
701 SkScalar rx, SkScalar ry, int startAngle,
702 SkPath::Direction dir, bool forceMoveTo) {
703 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
704 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 SkRect r;
707 r.set(-rx, -ry, rx, ry);
708
709 switch (startAngle) {
710 case 0:
711 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
712 break;
713 case 90:
714 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
715 break;
716 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
717 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000718 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 }
reed@google.comabf15c12011-01-18 20:35:51 +0000720
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721 SkScalar start = SkIntToScalar(startAngle);
722 SkScalar sweep = SkIntToScalar(90);
723 if (SkPath::kCCW_Direction == dir) {
724 start += sweep;
725 sweep = -sweep;
726 }
reed@google.comabf15c12011-01-18 20:35:51 +0000727
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728 path->arcTo(r, start, sweep, forceMoveTo);
729}
730
731void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
732 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000733 // abort before we invoke SkAutoPathBoundsUpdate()
734 if (rect.isEmpty()) {
735 return;
736 }
737
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738 SkAutoPathBoundsUpdate apbu(this, rect);
739
740 if (kCW_Direction == dir) {
741 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
742 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
743 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
744 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
745 } else {
746 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
747 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
748 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
749 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
750 }
751 this->close();
752}
753
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000754bool SkPath::hasOnlyMoveTos() const {
755 const uint8_t* verbs = fVerbs.begin();
756 const uint8_t* verbStop = fVerbs.end();
757 while (verbs != verbStop) {
758 if (*verbs == kLine_Verb ||
759 *verbs == kQuad_Verb ||
760 *verbs == kCubic_Verb) {
761 return false;
762 }
763 ++verbs;
764 }
765 return true;
766}
767
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000769 /* If addOval() is called after previous moveTo(),
770 this path is still marked as an oval. This is used to
771 fit into WebKit's calling sequences.
772 We can't simply check isEmpty() in this case, as additional
773 moveTo() would mark the path non empty.
774 */
775 fIsOval = hasOnlyMoveTos();
776
777 SkAutoDisableOvalCheck adoc(this);
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 SkAutoPathBoundsUpdate apbu(this, oval);
780
781 SkScalar cx = oval.centerX();
782 SkScalar cy = oval.centerY();
783 SkScalar rx = SkScalarHalf(oval.width());
784 SkScalar ry = SkScalarHalf(oval.height());
785#if 0 // these seem faster than using quads (1/2 the number of edges)
786 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
787 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
788
789 this->incReserve(13);
790 this->moveTo(cx + rx, cy);
791 if (dir == kCCW_Direction) {
792 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
793 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
794 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
795 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
796 } else {
797 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
798 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
799 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
800 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
801 }
802#else
803 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
804 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
805 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
806 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
807
808 /*
809 To handle imprecision in computing the center and radii, we revert to
810 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
811 to ensure that we don't exceed the oval's bounds *ever*, since we want
812 to use oval for our fast-bounds, rather than have to recompute it.
813 */
814 const SkScalar L = oval.fLeft; // cx - rx
815 const SkScalar T = oval.fTop; // cy - ry
816 const SkScalar R = oval.fRight; // cx + rx
817 const SkScalar B = oval.fBottom; // cy + ry
818
819 this->incReserve(17); // 8 quads + close
820 this->moveTo(R, cy);
821 if (dir == kCCW_Direction) {
822 this->quadTo( R, cy - sy, cx + mx, cy - my);
823 this->quadTo(cx + sx, T, cx , T);
824 this->quadTo(cx - sx, T, cx - mx, cy - my);
825 this->quadTo( L, cy - sy, L, cy );
826 this->quadTo( L, cy + sy, cx - mx, cy + my);
827 this->quadTo(cx - sx, B, cx , B);
828 this->quadTo(cx + sx, B, cx + mx, cy + my);
829 this->quadTo( R, cy + sy, R, cy );
830 } else {
831 this->quadTo( R, cy + sy, cx + mx, cy + my);
832 this->quadTo(cx + sx, B, cx , B);
833 this->quadTo(cx - sx, B, cx - mx, cy + my);
834 this->quadTo( L, cy + sy, L, cy );
835 this->quadTo( L, cy - sy, cx - mx, cy - my);
836 this->quadTo(cx - sx, T, cx , T);
837 this->quadTo(cx + sx, T, cx + mx, cy - my);
838 this->quadTo( R, cy - sy, R, cy );
839 }
840#endif
841 this->close();
842}
843
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000844bool SkPath::isOval(SkRect* rect) const {
845 if (fIsOval && rect) {
846 *rect = getBounds();
847 }
848
849 return fIsOval;
850}
851
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
853 if (r > 0) {
854 SkRect rect;
855 rect.set(x - r, y - r, x + r, y + r);
856 this->addOval(rect, dir);
857 }
858}
859
860#include "SkGeometry.h"
861
862static int build_arc_points(const SkRect& oval, SkScalar startAngle,
863 SkScalar sweepAngle,
864 SkPoint pts[kSkBuildQuadArcStorage]) {
865 SkVector start, stop;
866
867 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
868 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
869 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000870
871 /* If the sweep angle is nearly (but less than) 360, then due to precision
872 loss in radians-conversion and/or sin/cos, we may end up with coincident
873 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
874 of drawing a nearly complete circle (good).
875 e.g. canvas.drawArc(0, 359.99, ...)
876 -vs- canvas.drawArc(0, 359.9, ...)
877 We try to detect this edge case, and tweak the stop vector
878 */
879 if (start == stop) {
880 SkScalar sw = SkScalarAbs(sweepAngle);
881 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
882 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
883 // make a guess at a tiny angle (in radians) to tweak by
884 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
885 // not sure how much will be enough, so we use a loop
886 do {
887 stopRad -= deltaRad;
888 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
889 } while (start == stop);
890 }
891 }
892
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000894
reed@android.com8a1c16f2008-12-17 15:59:43 +0000895 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
896 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000897
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898 return SkBuildQuadArc(start, stop,
899 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
900 &matrix, pts);
901}
902
903void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
904 bool forceMoveTo) {
905 if (oval.width() < 0 || oval.height() < 0) {
906 return;
907 }
908
909 SkPoint pts[kSkBuildQuadArcStorage];
910 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
911 SkASSERT((count & 1) == 1);
912
913 if (fVerbs.count() == 0) {
914 forceMoveTo = true;
915 }
916 this->incReserve(count);
917 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
918 for (int i = 1; i < count; i += 2) {
919 this->quadTo(pts[i], pts[i+1]);
920 }
921}
922
923void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
924 SkScalar sweepAngle) {
925 if (oval.isEmpty() || 0 == sweepAngle) {
926 return;
927 }
928
929 const SkScalar kFullCircleAngle = SkIntToScalar(360);
930
931 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
932 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
933 return;
934 }
935
936 SkPoint pts[kSkBuildQuadArcStorage];
937 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
938
939 this->incReserve(count);
940 this->moveTo(pts[0]);
941 for (int i = 1; i < count; i += 2) {
942 this->quadTo(pts[i], pts[i+1]);
943 }
944}
945
946/*
947 Need to handle the case when the angle is sharp, and our computed end-points
948 for the arc go behind pt1 and/or p2...
949*/
950void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
951 SkScalar radius) {
952 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000953
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 // need to know our prev pt so we can construct tangent vectors
955 {
956 SkPoint start;
957 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000958 // Handle degenerate cases by adding a line to the first point and
959 // bailing out.
960 if ((x1 == start.fX && y1 == start.fY) ||
961 (x1 == x2 && y1 == y2) ||
962 radius == 0) {
963 this->lineTo(x1, y1);
964 return;
965 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966 before.setNormalize(x1 - start.fX, y1 - start.fY);
967 after.setNormalize(x2 - x1, y2 - y1);
968 }
reed@google.comabf15c12011-01-18 20:35:51 +0000969
reed@android.com8a1c16f2008-12-17 15:59:43 +0000970 SkScalar cosh = SkPoint::DotProduct(before, after);
971 SkScalar sinh = SkPoint::CrossProduct(before, after);
972
973 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000974 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 return;
976 }
reed@google.comabf15c12011-01-18 20:35:51 +0000977
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
979 if (dist < 0) {
980 dist = -dist;
981 }
982
983 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
984 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
985 SkRotationDirection arcDir;
986
987 // now turn before/after into normals
988 if (sinh > 0) {
989 before.rotateCCW();
990 after.rotateCCW();
991 arcDir = kCW_SkRotationDirection;
992 } else {
993 before.rotateCW();
994 after.rotateCW();
995 arcDir = kCCW_SkRotationDirection;
996 }
997
998 SkMatrix matrix;
999 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001000
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 matrix.setScale(radius, radius);
1002 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1003 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001004
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001006
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 this->incReserve(count);
1008 // [xx,yy] == pts[0]
1009 this->lineTo(xx, yy);
1010 for (int i = 1; i < count; i += 2) {
1011 this->quadTo(pts[i], pts[i+1]);
1012 }
1013}
1014
1015///////////////////////////////////////////////////////////////////////////////
1016
1017void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1018 SkMatrix matrix;
1019
1020 matrix.setTranslate(dx, dy);
1021 this->addPath(path, matrix);
1022}
1023
1024void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1025 this->incReserve(path.fPts.count());
1026
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001027 fIsOval = false;
1028
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001029 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 SkPoint pts[4];
1031 Verb verb;
1032
1033 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1034
1035 while ((verb = iter.next(pts)) != kDone_Verb) {
1036 switch (verb) {
1037 case kMove_Verb:
1038 proc(matrix, &pts[0], &pts[0], 1);
1039 this->moveTo(pts[0]);
1040 break;
1041 case kLine_Verb:
1042 proc(matrix, &pts[1], &pts[1], 1);
1043 this->lineTo(pts[1]);
1044 break;
1045 case kQuad_Verb:
1046 proc(matrix, &pts[1], &pts[1], 2);
1047 this->quadTo(pts[1], pts[2]);
1048 break;
1049 case kCubic_Verb:
1050 proc(matrix, &pts[1], &pts[1], 3);
1051 this->cubicTo(pts[1], pts[2], pts[3]);
1052 break;
1053 case kClose_Verb:
1054 this->close();
1055 break;
1056 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001057 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 }
1059 }
1060}
1061
1062///////////////////////////////////////////////////////////////////////////////
1063
1064static const uint8_t gPtsInVerb[] = {
1065 1, // kMove
1066 1, // kLine
1067 2, // kQuad
1068 3, // kCubic
1069 0, // kClose
1070 0 // kDone
1071};
1072
1073// ignore the initial moveto, and stop when the 1st contour ends
1074void SkPath::pathTo(const SkPath& path) {
1075 int i, vcount = path.fVerbs.count();
1076 if (vcount == 0) {
1077 return;
1078 }
1079
1080 this->incReserve(vcount);
1081
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001082 fIsOval = false;
1083
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 const uint8_t* verbs = path.fVerbs.begin();
1085 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1086
1087 SkASSERT(verbs[0] == kMove_Verb);
1088 for (i = 1; i < vcount; i++) {
1089 switch (verbs[i]) {
1090 case kLine_Verb:
1091 this->lineTo(pts[0].fX, pts[0].fY);
1092 break;
1093 case kQuad_Verb:
1094 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1095 break;
1096 case kCubic_Verb:
1097 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1098 pts[2].fX, pts[2].fY);
1099 break;
1100 case kClose_Verb:
1101 return;
1102 }
1103 pts += gPtsInVerb[verbs[i]];
1104 }
1105}
1106
1107// ignore the last point of the 1st contour
1108void SkPath::reversePathTo(const SkPath& path) {
1109 int i, vcount = path.fVerbs.count();
1110 if (vcount == 0) {
1111 return;
1112 }
1113
1114 this->incReserve(vcount);
1115
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001116 fIsOval = false;
1117
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 const uint8_t* verbs = path.fVerbs.begin();
1119 const SkPoint* pts = path.fPts.begin();
1120
1121 SkASSERT(verbs[0] == kMove_Verb);
1122 for (i = 1; i < vcount; i++) {
1123 int n = gPtsInVerb[verbs[i]];
1124 if (n == 0) {
1125 break;
1126 }
1127 pts += n;
1128 }
1129
1130 while (--i > 0) {
1131 switch (verbs[i]) {
1132 case kLine_Verb:
1133 this->lineTo(pts[-1].fX, pts[-1].fY);
1134 break;
1135 case kQuad_Verb:
1136 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1137 break;
1138 case kCubic_Verb:
1139 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1140 pts[-3].fX, pts[-3].fY);
1141 break;
1142 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001143 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144 break;
1145 }
1146 pts -= gPtsInVerb[verbs[i]];
1147 }
1148}
1149
reed@google.com63d73742012-01-10 15:33:12 +00001150void SkPath::reverseAddPath(const SkPath& src) {
1151 this->incReserve(src.fPts.count());
1152
1153 const SkPoint* startPts = src.fPts.begin();
1154 const SkPoint* pts = src.fPts.end();
1155 const uint8_t* startVerbs = src.fVerbs.begin();
1156 const uint8_t* verbs = src.fVerbs.end();
1157
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001158 fIsOval = false;
1159
reed@google.com63d73742012-01-10 15:33:12 +00001160 bool needMove = true;
1161 bool needClose = false;
1162 while (verbs > startVerbs) {
1163 uint8_t v = *--verbs;
1164 int n = gPtsInVerb[v];
1165
1166 if (needMove) {
1167 --pts;
1168 this->moveTo(pts->fX, pts->fY);
1169 needMove = false;
1170 }
1171 pts -= n;
1172 switch (v) {
1173 case kMove_Verb:
1174 if (needClose) {
1175 this->close();
1176 needClose = false;
1177 }
1178 needMove = true;
1179 pts += 1; // so we see the point in "if (needMove)" above
1180 break;
1181 case kLine_Verb:
1182 this->lineTo(pts[0]);
1183 break;
1184 case kQuad_Verb:
1185 this->quadTo(pts[1], pts[0]);
1186 break;
1187 case kCubic_Verb:
1188 this->cubicTo(pts[2], pts[1], pts[0]);
1189 break;
1190 case kClose_Verb:
1191 needClose = true;
1192 break;
1193 default:
1194 SkASSERT(!"unexpected verb");
1195 }
1196 }
1197}
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199///////////////////////////////////////////////////////////////////////////////
1200
1201void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1202 SkMatrix matrix;
1203
1204 matrix.setTranslate(dx, dy);
1205 this->transform(matrix, dst);
1206}
1207
1208#include "SkGeometry.h"
1209
1210static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1211 int level = 2) {
1212 if (--level >= 0) {
1213 SkPoint tmp[5];
1214
1215 SkChopQuadAtHalf(pts, tmp);
1216 subdivide_quad_to(path, &tmp[0], level);
1217 subdivide_quad_to(path, &tmp[2], level);
1218 } else {
1219 path->quadTo(pts[1], pts[2]);
1220 }
1221}
1222
1223static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1224 int level = 2) {
1225 if (--level >= 0) {
1226 SkPoint tmp[7];
1227
1228 SkChopCubicAtHalf(pts, tmp);
1229 subdivide_cubic_to(path, &tmp[0], level);
1230 subdivide_cubic_to(path, &tmp[3], level);
1231 } else {
1232 path->cubicTo(pts[1], pts[2], pts[3]);
1233 }
1234}
1235
1236void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1237 SkDEBUGCODE(this->validate();)
1238 if (dst == NULL) {
1239 dst = (SkPath*)this;
1240 }
1241
tomhudson@google.com8d430182011-06-06 19:11:19 +00001242 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 SkPath tmp;
1244 tmp.fFillType = fFillType;
1245
1246 SkPath::Iter iter(*this, false);
1247 SkPoint pts[4];
1248 SkPath::Verb verb;
1249
1250 while ((verb = iter.next(pts)) != kDone_Verb) {
1251 switch (verb) {
1252 case kMove_Verb:
1253 tmp.moveTo(pts[0]);
1254 break;
1255 case kLine_Verb:
1256 tmp.lineTo(pts[1]);
1257 break;
1258 case kQuad_Verb:
1259 subdivide_quad_to(&tmp, pts);
1260 break;
1261 case kCubic_Verb:
1262 subdivide_cubic_to(&tmp, pts);
1263 break;
1264 case kClose_Verb:
1265 tmp.close();
1266 break;
1267 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001268 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 break;
1270 }
1271 }
1272
1273 dst->swap(tmp);
1274 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1275 } else {
1276 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001277 // fBoundsIsDirty before we set it
1278 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001280 matrix.mapRect(&dst->fBounds, fBounds);
1281 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001283 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001284 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285 }
1286
1287 if (this != dst) {
1288 dst->fVerbs = fVerbs;
1289 dst->fPts.setCount(fPts.count());
1290 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001291 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001292 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001293 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001295
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001297
1298 if (fIsOval) {
1299 // It's an oval only if it stays a rect.
1300 dst->fIsOval = matrix.rectStaysRect();
1301 }
1302
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 SkDEBUGCODE(dst->validate();)
1304 }
1305}
1306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307///////////////////////////////////////////////////////////////////////////////
1308///////////////////////////////////////////////////////////////////////////////
1309
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001310enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001311 kEmptyContour_SegmentState, // The current contour is empty. We may be
1312 // starting processing or we may have just
1313 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001314 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1315 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1316 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317};
1318
1319SkPath::Iter::Iter() {
1320#ifdef SK_DEBUG
1321 fPts = NULL;
1322 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001323 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001324 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325#endif
1326 // need to init enough to make next() harmlessly return kDone_Verb
1327 fVerbs = NULL;
1328 fVerbStop = NULL;
1329 fNeedClose = false;
1330}
1331
1332SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1333 this->setPath(path, forceClose);
1334}
1335
1336void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1337 fPts = path.fPts.begin();
1338 fVerbs = path.fVerbs.begin();
1339 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001340 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001341 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 fForceClose = SkToU8(forceClose);
1343 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001344 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345}
1346
1347bool SkPath::Iter::isClosedContour() const {
1348 if (fVerbs == NULL || fVerbs == fVerbStop) {
1349 return false;
1350 }
1351 if (fForceClose) {
1352 return true;
1353 }
1354
1355 const uint8_t* verbs = fVerbs;
1356 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001357
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358 if (kMove_Verb == *verbs) {
1359 verbs += 1; // skip the initial moveto
1360 }
1361
1362 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001363 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 if (kMove_Verb == v) {
1365 break;
1366 }
1367 if (kClose_Verb == v) {
1368 return true;
1369 }
1370 }
1371 return false;
1372}
1373
1374SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1375 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001376 // A special case: if both points are NaN, SkPoint::operation== returns
1377 // false, but the iterator expects that they are treated as the same.
1378 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001379 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1380 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001381 return kClose_Verb;
1382 }
1383
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 if (pts) {
1385 pts[0] = fLastPt;
1386 pts[1] = fMoveTo;
1387 }
1388 fLastPt = fMoveTo;
1389 fCloseLine = true;
1390 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001391 } else {
1392 pts[0] = fMoveTo;
1393 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395}
1396
1397bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001398 if (fSegmentState == kAfterMove_SegmentState) {
1399 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 if (pts) {
1401 *pts = fMoveTo;
1402 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001403 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001405 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1406 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 if (pts) {
1408 *pts = fPts[-1];
1409 }
1410 }
1411 return false;
1412}
1413
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001414void SkPath::Iter::consumeDegenerateSegments() {
1415 // We need to step over anything that will not move the current draw point
1416 // forward before the next move is seen
1417 const uint8_t* lastMoveVerb = 0;
1418 const SkPoint* lastMovePt = 0;
1419 SkPoint lastPt = fLastPt;
1420 while (fVerbs != fVerbStop) {
1421 unsigned verb = *fVerbs;
1422 switch (verb) {
1423 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001424 // Keep a record of this most recent move
1425 lastMoveVerb = fVerbs;
1426 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001427 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001428 fVerbs++;
1429 fPts++;
1430 break;
1431
1432 case kClose_Verb:
1433 // A close when we are in a segment is always valid
1434 if (fSegmentState == kAfterPrimitive_SegmentState) {
1435 return;
1436 }
1437 // A close at any other time must be ignored
1438 fVerbs++;
1439 break;
1440
1441 case kLine_Verb:
1442 if (!IsLineDegenerate(lastPt, fPts[0])) {
1443 if (lastMoveVerb) {
1444 fVerbs = lastMoveVerb;
1445 fPts = lastMovePt;
1446 return;
1447 }
1448 return;
1449 }
1450 // Ignore this line and continue
1451 fVerbs++;
1452 fPts++;
1453 break;
1454
1455 case kQuad_Verb:
1456 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1457 if (lastMoveVerb) {
1458 fVerbs = lastMoveVerb;
1459 fPts = lastMovePt;
1460 return;
1461 }
1462 return;
1463 }
1464 // Ignore this line and continue
1465 fVerbs++;
1466 fPts += 2;
1467 break;
1468
1469 case kCubic_Verb:
1470 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1471 if (lastMoveVerb) {
1472 fVerbs = lastMoveVerb;
1473 fPts = lastMovePt;
1474 return;
1475 }
1476 return;
1477 }
1478 // Ignore this line and continue
1479 fVerbs++;
1480 fPts += 3;
1481 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001482
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001483 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001484 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001485 }
1486 }
1487}
1488
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001490 this->consumeDegenerateSegments();
1491
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001493 // Close the curve if requested and if there is some curve to close
1494 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 if (kLine_Verb == this->autoClose(pts)) {
1496 return kLine_Verb;
1497 }
1498 fNeedClose = false;
1499 return kClose_Verb;
1500 }
1501 return kDone_Verb;
1502 }
1503
1504 unsigned verb = *fVerbs++;
1505 const SkPoint* srcPts = fPts;
1506
1507 switch (verb) {
1508 case kMove_Verb:
1509 if (fNeedClose) {
1510 fVerbs -= 1;
1511 verb = this->autoClose(pts);
1512 if (verb == kClose_Verb) {
1513 fNeedClose = false;
1514 }
1515 return (Verb)verb;
1516 }
1517 if (fVerbs == fVerbStop) { // might be a trailing moveto
1518 return kDone_Verb;
1519 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001520 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 if (pts) {
1522 pts[0] = *srcPts;
1523 }
1524 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001525 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001526 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 fNeedClose = fForceClose;
1528 break;
1529 case kLine_Verb:
1530 if (this->cons_moveTo(pts)) {
1531 return kMove_Verb;
1532 }
1533 if (pts) {
1534 pts[1] = srcPts[0];
1535 }
1536 fLastPt = srcPts[0];
1537 fCloseLine = false;
1538 srcPts += 1;
1539 break;
1540 case kQuad_Verb:
1541 if (this->cons_moveTo(pts)) {
1542 return kMove_Verb;
1543 }
1544 if (pts) {
1545 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1546 }
1547 fLastPt = srcPts[1];
1548 srcPts += 2;
1549 break;
1550 case kCubic_Verb:
1551 if (this->cons_moveTo(pts)) {
1552 return kMove_Verb;
1553 }
1554 if (pts) {
1555 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1556 }
1557 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}