blob: 7b07f083bb79da86b105dd5137e4e09c6f3a2717 [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
reed@android.com8a1c16f2008-12-17 15:59:43 +000029/* This guy's constructor/destructor bracket a path editing operation. It is
30 used when we know the bounds of the amount we are going to add to the path
31 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000032
reed@android.com8a1c16f2008-12-17 15:59:43 +000033 It captures some state about the path up front (i.e. if it already has a
34 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000035 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000036
reed@android.com6b82d1a2009-06-03 02:35:01 +000037 It also notes if the path was originally empty, and if so, sets isConvex
38 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 */
40class SkAutoPathBoundsUpdate {
41public:
42 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
43 this->init(path);
44 }
45
46 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
47 SkScalar right, SkScalar bottom) {
48 fRect.set(left, top, right, bottom);
49 this->init(path);
50 }
reed@google.comabf15c12011-01-18 20:35:51 +000051
reed@android.com8a1c16f2008-12-17 15:59:43 +000052 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000053 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000055 fPath->fBounds = fRect;
56 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000058 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000059 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 }
61 }
reed@google.comabf15c12011-01-18 20:35:51 +000062
reed@android.com8a1c16f2008-12-17 15:59:43 +000063private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000064 SkPath* fPath;
65 SkRect fRect;
66 bool fDirty;
67 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000070 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000072 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000073 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000074 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000075 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 }
77};
78
reed@android.comd252db02009-04-01 18:31:44 +000079static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
81 bounds->set(0, 0, 0, 0);
82 } else {
83 bounds->set(pts.begin(), pts.count());
84// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
85 }
86}
87
88////////////////////////////////////////////////////////////////////////////
89
90/*
91 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +000092 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
94
95 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +000096 1. If we encounter degenerate segments, remove them
97 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
98 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
99 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100*/
101
102////////////////////////////////////////////////////////////////////////////
103
reed@google.comd335d1d2012-01-12 18:17:11 +0000104// flag to require a moveTo if we begin with something else, like lineTo etc.
105#define INITIAL_LASTMOVETOINDEX_VALUE ~0
106
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000107SkPath::SkPath()
108 : fFillType(kWinding_FillType)
109 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000110 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000111 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000112 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
djsollen@google.com56c69772011-11-08 19:00:26 +0000113#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000114 fGenerationID = 0;
115#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000116}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117
118SkPath::SkPath(const SkPath& src) {
119 SkDEBUGCODE(src.validate();)
120 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000121#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000122 // the assignment operator above increments the ID so correct for that here
123 fGenerationID--;
124#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125}
126
127SkPath::~SkPath() {
128 SkDEBUGCODE(this->validate();)
129}
130
131SkPath& SkPath::operator=(const SkPath& src) {
132 SkDEBUGCODE(src.validate();)
133
134 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000135 fBounds = src.fBounds;
136 fPts = src.fPts;
137 fVerbs = src.fVerbs;
138 fFillType = src.fFillType;
139 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000140 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000141 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000142 fLastMoveToIndex = src.fLastMoveToIndex;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000143 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 }
145 SkDEBUGCODE(this->validate();)
146 return *this;
147}
148
reed@android.com3abec1d2009-03-02 05:36:20 +0000149bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000150 // note: don't need to look at isConvex or bounds, since just comparing the
151 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000152
153 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
154 // since it is only a cache of info in the fVerbs, but its a fast way to
155 // notice a difference
156
reed@android.com3abec1d2009-03-02 05:36:20 +0000157 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000158 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
159 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000160}
161
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162void SkPath::swap(SkPath& other) {
163 SkASSERT(&other != NULL);
164
165 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000166 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167 fPts.swap(other.fPts);
168 fVerbs.swap(other.fVerbs);
169 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000170 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000171 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000172 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000173 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000174 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 }
176}
177
djsollen@google.com56c69772011-11-08 19:00:26 +0000178#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000179uint32_t SkPath::getGenerationID() const {
180 return fGenerationID;
181}
182#endif
183
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184void SkPath::reset() {
185 SkDEBUGCODE(this->validate();)
186
187 fPts.reset();
188 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000189 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000190 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000191 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000192 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000193 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194}
195
196void SkPath::rewind() {
197 SkDEBUGCODE(this->validate();)
198
199 fPts.rewind();
200 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000201 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000202 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000203 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000204 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000205 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206}
207
208bool SkPath::isEmpty() const {
209 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000210 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211}
212
caryclark@google.comf1316942011-07-26 19:54:45 +0000213/*
214 Determines if path is a rect by keeping track of changes in direction
215 and looking for a loop either clockwise or counterclockwise.
216
217 The direction is computed such that:
218 0: vertical up
219 1: horizontal right
220 2: vertical down
221 3: horizontal left
222
223A rectangle cycles up/right/down/left or up/left/down/right.
224
225The test fails if:
226 The path is closed, and followed by a line.
227 A second move creates a new endpoint.
228 A diagonal line is parsed.
229 There's more than four changes of direction.
230 There's a discontinuity on the line (e.g., a move in the middle)
231 The line reverses direction.
232 The rectangle doesn't complete a cycle.
233 The path contains a quadratic or cubic.
234 The path contains fewer than four points.
235 The final point isn't equal to the first point.
236
237It's OK if the path has:
238 Several colinear line segments composing a rectangle side.
239 Single points on the rectangle side.
240
241The direction takes advantage of the corners found since opposite sides
242must travel in opposite directions.
243
244FIXME: Allow colinear quads and cubics to be treated like lines.
245FIXME: If the API passes fill-only, return true if the filled stroke
246 is a rectangle, though the caller failed to close the path.
247 */
248bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000250
caryclark@google.comf1316942011-07-26 19:54:45 +0000251 int corners = 0;
252 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000253 first.set(0, 0);
254 last.set(0, 0);
255 int firstDirection = 0;
256 int lastDirection = 0;
257 int nextDirection = 0;
258 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000259 bool autoClose = false;
260 const uint8_t* verbs = fVerbs.begin();
261 const uint8_t* verbStop = fVerbs.end();
262 const SkPoint* pts = fPts.begin();
263 while (verbs != verbStop) {
264 switch (*verbs++) {
265 case kClose_Verb:
266 pts = fPts.begin();
267 autoClose = true;
268 case kLine_Verb: {
269 SkScalar left = last.fX;
270 SkScalar top = last.fY;
271 SkScalar right = pts->fX;
272 SkScalar bottom = pts->fY;
273 ++pts;
274 if (left != right && top != bottom) {
275 return false; // diagonal
276 }
277 if (left == right && top == bottom) {
278 break; // single point on side OK
279 }
280 nextDirection = (left != right) << 0 |
281 (left < right || top < bottom) << 1;
282 if (0 == corners) {
283 firstDirection = nextDirection;
284 first = last;
285 last = pts[-1];
286 corners = 1;
287 closedOrMoved = false;
288 break;
289 }
290 if (closedOrMoved) {
291 return false; // closed followed by a line
292 }
293 closedOrMoved = autoClose;
294 if (lastDirection != nextDirection) {
295 if (++corners > 4) {
296 return false; // too many direction changes
297 }
298 }
299 last = pts[-1];
300 if (lastDirection == nextDirection) {
301 break; // colinear segment
302 }
303 // Possible values for corners are 2, 3, and 4.
304 // When corners == 3, nextDirection opposes firstDirection.
305 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000306 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000307 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
308 if ((directionCycle ^ turn) != nextDirection) {
309 return false; // direction didn't follow cycle
310 }
311 break;
312 }
313 case kQuad_Verb:
314 case kCubic_Verb:
315 return false; // quadratic, cubic not allowed
316 case kMove_Verb:
317 last = *pts++;
318 closedOrMoved = true;
319 break;
320 }
321 lastDirection = nextDirection;
322 }
323 // Success if 4 corners and first point equals last
324 bool result = 4 == corners && first == last;
325 if (result && rect) {
326 *rect = getBounds();
327 }
328 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329}
330
331int SkPath::getPoints(SkPoint copy[], int max) const {
332 SkDEBUGCODE(this->validate();)
333
334 SkASSERT(max >= 0);
335 int count = fPts.count();
336 if (copy && max > 0 && count > 0) {
337 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
338 }
339 return count;
340}
341
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000342SkPoint SkPath::getPoint(int index) const {
343 if ((unsigned)index < (unsigned)fPts.count()) {
344 return fPts[index];
345 }
346 return SkPoint::Make(0, 0);
347}
348
reed@google.com294dd7b2011-10-11 11:58:32 +0000349bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 SkDEBUGCODE(this->validate();)
351
reed@google.com294dd7b2011-10-11 11:58:32 +0000352 int count = fPts.count();
353 if (count > 0) {
354 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 *lastPt = fPts[count - 1];
356 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000357 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000359 if (lastPt) {
360 lastPt->set(0, 0);
361 }
362 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363}
364
365void SkPath::setLastPt(SkScalar x, SkScalar y) {
366 SkDEBUGCODE(this->validate();)
367
368 int count = fPts.count();
369 if (count == 0) {
370 this->moveTo(x, y);
371 } else {
372 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000373 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374 }
375}
376
reed@android.comd252db02009-04-01 18:31:44 +0000377void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000378 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000379 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380
reed@android.comd252db02009-04-01 18:31:44 +0000381 fBoundsIsDirty = false;
382 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383}
384
reed@google.com04863fa2011-05-15 04:08:24 +0000385void SkPath::setConvexity(Convexity c) {
386 if (fConvexity != c) {
387 fConvexity = c;
388 GEN_ID_INC;
389 }
390}
391
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392//////////////////////////////////////////////////////////////////////////////
393// Construction methods
394
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000395#define DIRTY_AFTER_EDIT \
396 do { \
397 fBoundsIsDirty = true; \
398 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000399 } while (0)
400
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401void SkPath::incReserve(U16CPU inc) {
402 SkDEBUGCODE(this->validate();)
403
404 fVerbs.setReserve(fVerbs.count() + inc);
405 fPts.setReserve(fPts.count() + inc);
406
407 SkDEBUGCODE(this->validate();)
408}
409
410void SkPath::moveTo(SkScalar x, SkScalar y) {
411 SkDEBUGCODE(this->validate();)
412
413 int vc = fVerbs.count();
414 SkPoint* pt;
415
reed@google.comd335d1d2012-01-12 18:17:11 +0000416 // remember our index
417 fLastMoveToIndex = fPts.count();
418
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000419 pt = fPts.append();
420 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421 pt->set(x, y);
422
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000423 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000424 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425}
426
427void SkPath::rMoveTo(SkScalar x, SkScalar y) {
428 SkPoint pt;
429 this->getLastPt(&pt);
430 this->moveTo(pt.fX + x, pt.fY + y);
431}
432
reed@google.comd335d1d2012-01-12 18:17:11 +0000433void SkPath::injectMoveToIfNeeded() {
434 if (fLastMoveToIndex < 0) {
435 SkScalar x, y;
436 if (fVerbs.count() == 0) {
437 x = y = 0;
438 } else {
439 const SkPoint& pt = fPts[~fLastMoveToIndex];
440 x = pt.fX;
441 y = pt.fY;
442 }
443 this->moveTo(x, y);
444 }
445}
446
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447void SkPath::lineTo(SkScalar x, SkScalar y) {
448 SkDEBUGCODE(this->validate();)
449
reed@google.comd335d1d2012-01-12 18:17:11 +0000450 this->injectMoveToIfNeeded();
451
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452 fPts.append()->set(x, y);
453 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000454 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000456 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000457 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458}
459
460void SkPath::rLineTo(SkScalar x, SkScalar y) {
461 SkPoint pt;
462 this->getLastPt(&pt);
463 this->lineTo(pt.fX + x, pt.fY + y);
464}
465
466void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
467 SkDEBUGCODE(this->validate();)
468
reed@google.comd335d1d2012-01-12 18:17:11 +0000469 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470
471 SkPoint* pts = fPts.append(2);
472 pts[0].set(x1, y1);
473 pts[1].set(x2, y2);
474 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000475 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000477 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000478 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479}
480
481void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
482 SkPoint pt;
483 this->getLastPt(&pt);
484 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
485}
486
487void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
488 SkScalar x3, SkScalar y3) {
489 SkDEBUGCODE(this->validate();)
490
reed@google.comd335d1d2012-01-12 18:17:11 +0000491 this->injectMoveToIfNeeded();
492
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493 SkPoint* pts = fPts.append(3);
494 pts[0].set(x1, y1);
495 pts[1].set(x2, y2);
496 pts[2].set(x3, y3);
497 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000498 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000499
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000500 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000501 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502}
503
504void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
505 SkScalar x3, SkScalar y3) {
506 SkPoint pt;
507 this->getLastPt(&pt);
508 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
509 pt.fX + x3, pt.fY + y3);
510}
511
512void SkPath::close() {
513 SkDEBUGCODE(this->validate();)
514
515 int count = fVerbs.count();
516 if (count > 0) {
517 switch (fVerbs[count - 1]) {
518 case kLine_Verb:
519 case kQuad_Verb:
520 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000521 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000523 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000524 break;
525 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000526 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527 break;
528 }
529 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000530
531 // signal that we need a moveTo to follow us (unless we're done)
532#if 0
533 if (fLastMoveToIndex >= 0) {
534 fLastMoveToIndex = ~fLastMoveToIndex;
535 }
536#else
537 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
538#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000539}
540
541///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000542
reed@android.com8a1c16f2008-12-17 15:59:43 +0000543void SkPath::addRect(const SkRect& rect, Direction dir) {
544 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
545}
546
547void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
548 SkScalar bottom, Direction dir) {
549 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
550
551 this->incReserve(5);
552
553 this->moveTo(left, top);
554 if (dir == kCCW_Direction) {
555 this->lineTo(left, bottom);
556 this->lineTo(right, bottom);
557 this->lineTo(right, top);
558 } else {
559 this->lineTo(right, top);
560 this->lineTo(right, bottom);
561 this->lineTo(left, bottom);
562 }
563 this->close();
564}
565
566#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
567
568void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
569 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 SkScalar w = rect.width();
571 SkScalar halfW = SkScalarHalf(w);
572 SkScalar h = rect.height();
573 SkScalar halfH = SkScalarHalf(h);
574
575 if (halfW <= 0 || halfH <= 0) {
576 return;
577 }
578
reed@google.comabf15c12011-01-18 20:35:51 +0000579 bool skip_hori = rx >= halfW;
580 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581
582 if (skip_hori && skip_vert) {
583 this->addOval(rect, dir);
584 return;
585 }
reed@google.comabf15c12011-01-18 20:35:51 +0000586
587 SkAutoPathBoundsUpdate apbu(this, rect);
588
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 if (skip_hori) {
590 rx = halfW;
591 } else if (skip_vert) {
592 ry = halfH;
593 }
594
595 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
596 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
597
598 this->incReserve(17);
599 this->moveTo(rect.fRight - rx, rect.fTop);
600 if (dir == kCCW_Direction) {
601 if (!skip_hori) {
602 this->lineTo(rect.fLeft + rx, rect.fTop); // top
603 }
604 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
605 rect.fLeft, rect.fTop + ry - sy,
606 rect.fLeft, rect.fTop + ry); // top-left
607 if (!skip_vert) {
608 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
609 }
610 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
611 rect.fLeft + rx - sx, rect.fBottom,
612 rect.fLeft + rx, rect.fBottom); // bot-left
613 if (!skip_hori) {
614 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
615 }
616 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
617 rect.fRight, rect.fBottom - ry + sy,
618 rect.fRight, rect.fBottom - ry); // bot-right
619 if (!skip_vert) {
620 this->lineTo(rect.fRight, rect.fTop + ry);
621 }
622 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
623 rect.fRight - rx + sx, rect.fTop,
624 rect.fRight - rx, rect.fTop); // top-right
625 } else {
626 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
627 rect.fRight, rect.fTop + ry - sy,
628 rect.fRight, rect.fTop + ry); // top-right
629 if (!skip_vert) {
630 this->lineTo(rect.fRight, rect.fBottom - ry);
631 }
632 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
633 rect.fRight - rx + sx, rect.fBottom,
634 rect.fRight - rx, rect.fBottom); // bot-right
635 if (!skip_hori) {
636 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
637 }
638 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
639 rect.fLeft, rect.fBottom - ry + sy,
640 rect.fLeft, rect.fBottom - ry); // bot-left
641 if (!skip_vert) {
642 this->lineTo(rect.fLeft, rect.fTop + ry); // left
643 }
644 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
645 rect.fLeft + rx - sx, rect.fTop,
646 rect.fLeft + rx, rect.fTop); // top-left
647 if (!skip_hori) {
648 this->lineTo(rect.fRight - rx, rect.fTop); // top
649 }
650 }
651 this->close();
652}
653
654static void add_corner_arc(SkPath* path, const SkRect& rect,
655 SkScalar rx, SkScalar ry, int startAngle,
656 SkPath::Direction dir, bool forceMoveTo) {
657 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
658 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000659
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 SkRect r;
661 r.set(-rx, -ry, rx, ry);
662
663 switch (startAngle) {
664 case 0:
665 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
666 break;
667 case 90:
668 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
669 break;
670 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
671 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000672 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 }
reed@google.comabf15c12011-01-18 20:35:51 +0000674
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675 SkScalar start = SkIntToScalar(startAngle);
676 SkScalar sweep = SkIntToScalar(90);
677 if (SkPath::kCCW_Direction == dir) {
678 start += sweep;
679 sweep = -sweep;
680 }
reed@google.comabf15c12011-01-18 20:35:51 +0000681
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 path->arcTo(r, start, sweep, forceMoveTo);
683}
684
685void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
686 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000687 // abort before we invoke SkAutoPathBoundsUpdate()
688 if (rect.isEmpty()) {
689 return;
690 }
691
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 SkAutoPathBoundsUpdate apbu(this, rect);
693
694 if (kCW_Direction == dir) {
695 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
696 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
697 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
698 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
699 } else {
700 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
701 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
702 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
703 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
704 }
705 this->close();
706}
707
708void SkPath::addOval(const SkRect& oval, Direction dir) {
709 SkAutoPathBoundsUpdate apbu(this, oval);
710
711 SkScalar cx = oval.centerX();
712 SkScalar cy = oval.centerY();
713 SkScalar rx = SkScalarHalf(oval.width());
714 SkScalar ry = SkScalarHalf(oval.height());
715#if 0 // these seem faster than using quads (1/2 the number of edges)
716 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
717 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
718
719 this->incReserve(13);
720 this->moveTo(cx + rx, cy);
721 if (dir == kCCW_Direction) {
722 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
723 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
724 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
725 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
726 } else {
727 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
728 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
729 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
730 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
731 }
732#else
733 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
734 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
735 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
736 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
737
738 /*
739 To handle imprecision in computing the center and radii, we revert to
740 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
741 to ensure that we don't exceed the oval's bounds *ever*, since we want
742 to use oval for our fast-bounds, rather than have to recompute it.
743 */
744 const SkScalar L = oval.fLeft; // cx - rx
745 const SkScalar T = oval.fTop; // cy - ry
746 const SkScalar R = oval.fRight; // cx + rx
747 const SkScalar B = oval.fBottom; // cy + ry
748
749 this->incReserve(17); // 8 quads + close
750 this->moveTo(R, cy);
751 if (dir == kCCW_Direction) {
752 this->quadTo( R, cy - sy, cx + mx, cy - my);
753 this->quadTo(cx + sx, T, cx , T);
754 this->quadTo(cx - sx, T, cx - mx, cy - my);
755 this->quadTo( L, cy - sy, L, cy );
756 this->quadTo( L, cy + sy, cx - mx, cy + my);
757 this->quadTo(cx - sx, B, cx , B);
758 this->quadTo(cx + sx, B, cx + mx, cy + my);
759 this->quadTo( R, cy + sy, R, cy );
760 } else {
761 this->quadTo( R, cy + sy, cx + mx, cy + my);
762 this->quadTo(cx + sx, B, cx , B);
763 this->quadTo(cx - sx, B, cx - mx, cy + my);
764 this->quadTo( L, cy + sy, L, cy );
765 this->quadTo( L, cy - sy, cx - mx, cy - my);
766 this->quadTo(cx - sx, T, cx , T);
767 this->quadTo(cx + sx, T, cx + mx, cy - my);
768 this->quadTo( R, cy - sy, R, cy );
769 }
770#endif
771 this->close();
772}
773
774void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
775 if (r > 0) {
776 SkRect rect;
777 rect.set(x - r, y - r, x + r, y + r);
778 this->addOval(rect, dir);
779 }
780}
781
782#include "SkGeometry.h"
783
784static int build_arc_points(const SkRect& oval, SkScalar startAngle,
785 SkScalar sweepAngle,
786 SkPoint pts[kSkBuildQuadArcStorage]) {
787 SkVector start, stop;
788
789 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
790 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
791 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000792
793 /* If the sweep angle is nearly (but less than) 360, then due to precision
794 loss in radians-conversion and/or sin/cos, we may end up with coincident
795 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
796 of drawing a nearly complete circle (good).
797 e.g. canvas.drawArc(0, 359.99, ...)
798 -vs- canvas.drawArc(0, 359.9, ...)
799 We try to detect this edge case, and tweak the stop vector
800 */
801 if (start == stop) {
802 SkScalar sw = SkScalarAbs(sweepAngle);
803 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
804 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
805 // make a guess at a tiny angle (in radians) to tweak by
806 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
807 // not sure how much will be enough, so we use a loop
808 do {
809 stopRad -= deltaRad;
810 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
811 } while (start == stop);
812 }
813 }
814
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000816
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
818 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000819
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 return SkBuildQuadArc(start, stop,
821 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
822 &matrix, pts);
823}
824
825void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
826 bool forceMoveTo) {
827 if (oval.width() < 0 || oval.height() < 0) {
828 return;
829 }
830
831 SkPoint pts[kSkBuildQuadArcStorage];
832 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
833 SkASSERT((count & 1) == 1);
834
835 if (fVerbs.count() == 0) {
836 forceMoveTo = true;
837 }
838 this->incReserve(count);
839 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
840 for (int i = 1; i < count; i += 2) {
841 this->quadTo(pts[i], pts[i+1]);
842 }
843}
844
845void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
846 SkScalar sweepAngle) {
847 if (oval.isEmpty() || 0 == sweepAngle) {
848 return;
849 }
850
851 const SkScalar kFullCircleAngle = SkIntToScalar(360);
852
853 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
854 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
855 return;
856 }
857
858 SkPoint pts[kSkBuildQuadArcStorage];
859 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
860
861 this->incReserve(count);
862 this->moveTo(pts[0]);
863 for (int i = 1; i < count; i += 2) {
864 this->quadTo(pts[i], pts[i+1]);
865 }
866}
867
868/*
869 Need to handle the case when the angle is sharp, and our computed end-points
870 for the arc go behind pt1 and/or p2...
871*/
872void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
873 SkScalar radius) {
874 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000875
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 // need to know our prev pt so we can construct tangent vectors
877 {
878 SkPoint start;
879 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000880 // Handle degenerate cases by adding a line to the first point and
881 // bailing out.
882 if ((x1 == start.fX && y1 == start.fY) ||
883 (x1 == x2 && y1 == y2) ||
884 radius == 0) {
885 this->lineTo(x1, y1);
886 return;
887 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 before.setNormalize(x1 - start.fX, y1 - start.fY);
889 after.setNormalize(x2 - x1, y2 - y1);
890 }
reed@google.comabf15c12011-01-18 20:35:51 +0000891
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 SkScalar cosh = SkPoint::DotProduct(before, after);
893 SkScalar sinh = SkPoint::CrossProduct(before, after);
894
895 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000896 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 return;
898 }
reed@google.comabf15c12011-01-18 20:35:51 +0000899
reed@android.com8a1c16f2008-12-17 15:59:43 +0000900 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
901 if (dist < 0) {
902 dist = -dist;
903 }
904
905 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
906 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
907 SkRotationDirection arcDir;
908
909 // now turn before/after into normals
910 if (sinh > 0) {
911 before.rotateCCW();
912 after.rotateCCW();
913 arcDir = kCW_SkRotationDirection;
914 } else {
915 before.rotateCW();
916 after.rotateCW();
917 arcDir = kCCW_SkRotationDirection;
918 }
919
920 SkMatrix matrix;
921 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000922
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923 matrix.setScale(radius, radius);
924 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
925 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000926
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000928
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 this->incReserve(count);
930 // [xx,yy] == pts[0]
931 this->lineTo(xx, yy);
932 for (int i = 1; i < count; i += 2) {
933 this->quadTo(pts[i], pts[i+1]);
934 }
935}
936
937///////////////////////////////////////////////////////////////////////////////
938
939void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
940 SkMatrix matrix;
941
942 matrix.setTranslate(dx, dy);
943 this->addPath(path, matrix);
944}
945
946void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
947 this->incReserve(path.fPts.count());
948
schenney@chromium.org6630d8d2012-01-04 21:05:51 +0000949 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 SkPoint pts[4];
951 Verb verb;
952
953 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
954
955 while ((verb = iter.next(pts)) != kDone_Verb) {
956 switch (verb) {
957 case kMove_Verb:
958 proc(matrix, &pts[0], &pts[0], 1);
959 this->moveTo(pts[0]);
960 break;
961 case kLine_Verb:
962 proc(matrix, &pts[1], &pts[1], 1);
963 this->lineTo(pts[1]);
964 break;
965 case kQuad_Verb:
966 proc(matrix, &pts[1], &pts[1], 2);
967 this->quadTo(pts[1], pts[2]);
968 break;
969 case kCubic_Verb:
970 proc(matrix, &pts[1], &pts[1], 3);
971 this->cubicTo(pts[1], pts[2], pts[3]);
972 break;
973 case kClose_Verb:
974 this->close();
975 break;
976 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000977 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 }
979 }
980}
981
982///////////////////////////////////////////////////////////////////////////////
983
984static const uint8_t gPtsInVerb[] = {
985 1, // kMove
986 1, // kLine
987 2, // kQuad
988 3, // kCubic
989 0, // kClose
990 0 // kDone
991};
992
993// ignore the initial moveto, and stop when the 1st contour ends
994void SkPath::pathTo(const SkPath& path) {
995 int i, vcount = path.fVerbs.count();
996 if (vcount == 0) {
997 return;
998 }
999
1000 this->incReserve(vcount);
1001
1002 const uint8_t* verbs = path.fVerbs.begin();
1003 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1004
1005 SkASSERT(verbs[0] == kMove_Verb);
1006 for (i = 1; i < vcount; i++) {
1007 switch (verbs[i]) {
1008 case kLine_Verb:
1009 this->lineTo(pts[0].fX, pts[0].fY);
1010 break;
1011 case kQuad_Verb:
1012 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1013 break;
1014 case kCubic_Verb:
1015 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1016 pts[2].fX, pts[2].fY);
1017 break;
1018 case kClose_Verb:
1019 return;
1020 }
1021 pts += gPtsInVerb[verbs[i]];
1022 }
1023}
1024
1025// ignore the last point of the 1st contour
1026void SkPath::reversePathTo(const SkPath& path) {
1027 int i, vcount = path.fVerbs.count();
1028 if (vcount == 0) {
1029 return;
1030 }
1031
1032 this->incReserve(vcount);
1033
1034 const uint8_t* verbs = path.fVerbs.begin();
1035 const SkPoint* pts = path.fPts.begin();
1036
1037 SkASSERT(verbs[0] == kMove_Verb);
1038 for (i = 1; i < vcount; i++) {
1039 int n = gPtsInVerb[verbs[i]];
1040 if (n == 0) {
1041 break;
1042 }
1043 pts += n;
1044 }
1045
1046 while (--i > 0) {
1047 switch (verbs[i]) {
1048 case kLine_Verb:
1049 this->lineTo(pts[-1].fX, pts[-1].fY);
1050 break;
1051 case kQuad_Verb:
1052 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1053 break;
1054 case kCubic_Verb:
1055 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1056 pts[-3].fX, pts[-3].fY);
1057 break;
1058 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001059 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 break;
1061 }
1062 pts -= gPtsInVerb[verbs[i]];
1063 }
1064}
1065
reed@google.com63d73742012-01-10 15:33:12 +00001066void SkPath::reverseAddPath(const SkPath& src) {
1067 this->incReserve(src.fPts.count());
1068
1069 const SkPoint* startPts = src.fPts.begin();
1070 const SkPoint* pts = src.fPts.end();
1071 const uint8_t* startVerbs = src.fVerbs.begin();
1072 const uint8_t* verbs = src.fVerbs.end();
1073
1074 bool needMove = true;
1075 bool needClose = false;
1076 while (verbs > startVerbs) {
1077 uint8_t v = *--verbs;
1078 int n = gPtsInVerb[v];
1079
1080 if (needMove) {
1081 --pts;
1082 this->moveTo(pts->fX, pts->fY);
1083 needMove = false;
1084 }
1085 pts -= n;
1086 switch (v) {
1087 case kMove_Verb:
1088 if (needClose) {
1089 this->close();
1090 needClose = false;
1091 }
1092 needMove = true;
1093 pts += 1; // so we see the point in "if (needMove)" above
1094 break;
1095 case kLine_Verb:
1096 this->lineTo(pts[0]);
1097 break;
1098 case kQuad_Verb:
1099 this->quadTo(pts[1], pts[0]);
1100 break;
1101 case kCubic_Verb:
1102 this->cubicTo(pts[2], pts[1], pts[0]);
1103 break;
1104 case kClose_Verb:
1105 needClose = true;
1106 break;
1107 default:
1108 SkASSERT(!"unexpected verb");
1109 }
1110 }
1111}
1112
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113///////////////////////////////////////////////////////////////////////////////
1114
1115void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1116 SkMatrix matrix;
1117
1118 matrix.setTranslate(dx, dy);
1119 this->transform(matrix, dst);
1120}
1121
1122#include "SkGeometry.h"
1123
1124static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1125 int level = 2) {
1126 if (--level >= 0) {
1127 SkPoint tmp[5];
1128
1129 SkChopQuadAtHalf(pts, tmp);
1130 subdivide_quad_to(path, &tmp[0], level);
1131 subdivide_quad_to(path, &tmp[2], level);
1132 } else {
1133 path->quadTo(pts[1], pts[2]);
1134 }
1135}
1136
1137static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1138 int level = 2) {
1139 if (--level >= 0) {
1140 SkPoint tmp[7];
1141
1142 SkChopCubicAtHalf(pts, tmp);
1143 subdivide_cubic_to(path, &tmp[0], level);
1144 subdivide_cubic_to(path, &tmp[3], level);
1145 } else {
1146 path->cubicTo(pts[1], pts[2], pts[3]);
1147 }
1148}
1149
1150void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1151 SkDEBUGCODE(this->validate();)
1152 if (dst == NULL) {
1153 dst = (SkPath*)this;
1154 }
1155
tomhudson@google.com8d430182011-06-06 19:11:19 +00001156 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157 SkPath tmp;
1158 tmp.fFillType = fFillType;
1159
1160 SkPath::Iter iter(*this, false);
1161 SkPoint pts[4];
1162 SkPath::Verb verb;
1163
1164 while ((verb = iter.next(pts)) != kDone_Verb) {
1165 switch (verb) {
1166 case kMove_Verb:
1167 tmp.moveTo(pts[0]);
1168 break;
1169 case kLine_Verb:
1170 tmp.lineTo(pts[1]);
1171 break;
1172 case kQuad_Verb:
1173 subdivide_quad_to(&tmp, pts);
1174 break;
1175 case kCubic_Verb:
1176 subdivide_cubic_to(&tmp, pts);
1177 break;
1178 case kClose_Verb:
1179 tmp.close();
1180 break;
1181 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001182 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 break;
1184 }
1185 }
1186
1187 dst->swap(tmp);
1188 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1189 } else {
1190 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001191 // fBoundsIsDirty before we set it
1192 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001194 matrix.mapRect(&dst->fBounds, fBounds);
1195 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001196 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001197 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001198 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 }
1200
1201 if (this != dst) {
1202 dst->fVerbs = fVerbs;
1203 dst->fPts.setCount(fPts.count());
1204 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001205 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001206 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 }
1208 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1209 SkDEBUGCODE(dst->validate();)
1210 }
1211}
1212
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213///////////////////////////////////////////////////////////////////////////////
1214///////////////////////////////////////////////////////////////////////////////
1215
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001216enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001217 kEmptyContour_SegmentState, // The current contour is empty. We may be
1218 // starting processing or we may have just
1219 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001220 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1221 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1222 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223};
1224
1225SkPath::Iter::Iter() {
1226#ifdef SK_DEBUG
1227 fPts = NULL;
1228 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001229 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001230 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231#endif
1232 // need to init enough to make next() harmlessly return kDone_Verb
1233 fVerbs = NULL;
1234 fVerbStop = NULL;
1235 fNeedClose = false;
1236}
1237
1238SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1239 this->setPath(path, forceClose);
1240}
1241
1242void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1243 fPts = path.fPts.begin();
1244 fVerbs = path.fVerbs.begin();
1245 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001246 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001247 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 fForceClose = SkToU8(forceClose);
1249 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001250 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251}
1252
1253bool SkPath::Iter::isClosedContour() const {
1254 if (fVerbs == NULL || fVerbs == fVerbStop) {
1255 return false;
1256 }
1257 if (fForceClose) {
1258 return true;
1259 }
1260
1261 const uint8_t* verbs = fVerbs;
1262 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001263
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 if (kMove_Verb == *verbs) {
1265 verbs += 1; // skip the initial moveto
1266 }
1267
1268 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001269 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 if (kMove_Verb == v) {
1271 break;
1272 }
1273 if (kClose_Verb == v) {
1274 return true;
1275 }
1276 }
1277 return false;
1278}
1279
1280SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1281 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001282 // A special case: if both points are NaN, SkPoint::operation== returns
1283 // false, but the iterator expects that they are treated as the same.
1284 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001285 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1286 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001287 return kClose_Verb;
1288 }
1289
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290 if (pts) {
1291 pts[0] = fLastPt;
1292 pts[1] = fMoveTo;
1293 }
1294 fLastPt = fMoveTo;
1295 fCloseLine = true;
1296 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001297 } else {
1298 pts[0] = fMoveTo;
1299 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301}
1302
1303bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001304 if (fSegmentState == kAfterMove_SegmentState) {
1305 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 if (pts) {
1307 *pts = fMoveTo;
1308 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001309 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001311 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1312 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 if (pts) {
1314 *pts = fPts[-1];
1315 }
1316 }
1317 return false;
1318}
1319
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001320void SkPath::Iter::consumeDegenerateSegments() {
1321 // We need to step over anything that will not move the current draw point
1322 // forward before the next move is seen
1323 const uint8_t* lastMoveVerb = 0;
1324 const SkPoint* lastMovePt = 0;
1325 SkPoint lastPt = fLastPt;
1326 while (fVerbs != fVerbStop) {
1327 unsigned verb = *fVerbs;
1328 switch (verb) {
1329 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001330 // Keep a record of this most recent move
1331 lastMoveVerb = fVerbs;
1332 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001333 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001334 fVerbs++;
1335 fPts++;
1336 break;
1337
1338 case kClose_Verb:
1339 // A close when we are in a segment is always valid
1340 if (fSegmentState == kAfterPrimitive_SegmentState) {
1341 return;
1342 }
1343 // A close at any other time must be ignored
1344 fVerbs++;
1345 break;
1346
1347 case kLine_Verb:
1348 if (!IsLineDegenerate(lastPt, fPts[0])) {
1349 if (lastMoveVerb) {
1350 fVerbs = lastMoveVerb;
1351 fPts = lastMovePt;
1352 return;
1353 }
1354 return;
1355 }
1356 // Ignore this line and continue
1357 fVerbs++;
1358 fPts++;
1359 break;
1360
1361 case kQuad_Verb:
1362 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1363 if (lastMoveVerb) {
1364 fVerbs = lastMoveVerb;
1365 fPts = lastMovePt;
1366 return;
1367 }
1368 return;
1369 }
1370 // Ignore this line and continue
1371 fVerbs++;
1372 fPts += 2;
1373 break;
1374
1375 case kCubic_Verb:
1376 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1377 if (lastMoveVerb) {
1378 fVerbs = lastMoveVerb;
1379 fPts = lastMovePt;
1380 return;
1381 }
1382 return;
1383 }
1384 // Ignore this line and continue
1385 fVerbs++;
1386 fPts += 3;
1387 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001388
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001389 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001390 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001391 }
1392 }
1393}
1394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001396 this->consumeDegenerateSegments();
1397
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001399 // Close the curve if requested and if there is some curve to close
1400 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 if (kLine_Verb == this->autoClose(pts)) {
1402 return kLine_Verb;
1403 }
1404 fNeedClose = false;
1405 return kClose_Verb;
1406 }
1407 return kDone_Verb;
1408 }
1409
1410 unsigned verb = *fVerbs++;
1411 const SkPoint* srcPts = fPts;
1412
1413 switch (verb) {
1414 case kMove_Verb:
1415 if (fNeedClose) {
1416 fVerbs -= 1;
1417 verb = this->autoClose(pts);
1418 if (verb == kClose_Verb) {
1419 fNeedClose = false;
1420 }
1421 return (Verb)verb;
1422 }
1423 if (fVerbs == fVerbStop) { // might be a trailing moveto
1424 return kDone_Verb;
1425 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001426 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 if (pts) {
1428 pts[0] = *srcPts;
1429 }
1430 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001431 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001432 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 fNeedClose = fForceClose;
1434 break;
1435 case kLine_Verb:
1436 if (this->cons_moveTo(pts)) {
1437 return kMove_Verb;
1438 }
1439 if (pts) {
1440 pts[1] = srcPts[0];
1441 }
1442 fLastPt = srcPts[0];
1443 fCloseLine = false;
1444 srcPts += 1;
1445 break;
1446 case kQuad_Verb:
1447 if (this->cons_moveTo(pts)) {
1448 return kMove_Verb;
1449 }
1450 if (pts) {
1451 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1452 }
1453 fLastPt = srcPts[1];
1454 srcPts += 2;
1455 break;
1456 case kCubic_Verb:
1457 if (this->cons_moveTo(pts)) {
1458 return kMove_Verb;
1459 }
1460 if (pts) {
1461 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1462 }
1463 fLastPt = srcPts[2];
1464 srcPts += 3;
1465 break;
1466 case kClose_Verb:
1467 verb = this->autoClose(pts);
1468 if (verb == kLine_Verb) {
1469 fVerbs -= 1;
1470 } else {
1471 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001472 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001474 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 break;
1476 }
1477 fPts = srcPts;
1478 return (Verb)verb;
1479}
1480
1481///////////////////////////////////////////////////////////////////////////////
1482
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001483SkPath::RawIter::RawIter() {
1484#ifdef SK_DEBUG
1485 fPts = NULL;
1486 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1487#endif
1488 // need to init enough to make next() harmlessly return kDone_Verb
1489 fVerbs = NULL;
1490 fVerbStop = NULL;
1491}
1492
1493SkPath::RawIter::RawIter(const SkPath& path) {
1494 this->setPath(path);
1495}
1496
1497void SkPath::RawIter::setPath(const SkPath& path) {
1498 fPts = path.fPts.begin();
1499 fVerbs = path.fVerbs.begin();
1500 fVerbStop = path.fVerbs.end();
1501 fMoveTo.fX = fMoveTo.fY = 0;
1502 fLastPt.fX = fLastPt.fY = 0;
1503}
1504
1505SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1506 if (fVerbs == fVerbStop) {
1507 return kDone_Verb;
1508 }
1509
1510 unsigned verb = *fVerbs++;
1511 const SkPoint* srcPts = fPts;
1512
1513 switch (verb) {
1514 case kMove_Verb:
1515 if (pts) {
1516 pts[0] = *srcPts;
1517 }
1518 fMoveTo = srcPts[0];
1519 fLastPt = fMoveTo;
1520 srcPts += 1;
1521 break;
1522 case kLine_Verb:
1523 if (pts) {
1524 pts[0] = fLastPt;
1525 pts[1] = srcPts[0];
1526 }
1527 fLastPt = srcPts[0];
1528 srcPts += 1;
1529 break;
1530 case kQuad_Verb:
1531 if (pts) {
1532 pts[0] = fLastPt;
1533 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1534 }
1535 fLastPt = srcPts[1];
1536 srcPts += 2;
1537 break;
1538 case kCubic_Verb:
1539 if (pts) {
1540 pts[0] = fLastPt;
1541 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1542 }
1543 fLastPt = srcPts[2];
1544 srcPts += 3;
1545 break;
1546 case kClose_Verb:
1547 fLastPt = fMoveTo;
1548 if (pts) {
1549 pts[0] = fMoveTo;
1550 }
1551 break;
1552 }
1553 fPts = srcPts;
1554 return (Verb)verb;
1555}
1556
1557///////////////////////////////////////////////////////////////////////////////
1558
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559/*
1560 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1561*/
1562
reed@google.com73945652011-04-25 19:04:27 +00001563void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 SkDEBUGCODE(this->validate();)
1565
1566 buffer.write32(fPts.count());
1567 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001568 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1570 buffer.writePad(fVerbs.begin(), fVerbs.count());
1571}
1572
reed@google.com73945652011-04-25 19:04:27 +00001573void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 fPts.setCount(buffer.readS32());
1575 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001576 uint32_t packed = buffer.readS32();
1577 fFillType = packed >> 8;
1578 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1580 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001581
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001582 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001583 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584
1585 SkDEBUGCODE(this->validate();)
1586}
1587
1588///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590void SkPath::dump(bool forceClose, const char title[]) const {
1591 Iter iter(*this, forceClose);
1592 SkPoint pts[4];
1593 Verb verb;
1594
1595 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1596 title ? title : "");
1597
1598 while ((verb = iter.next(pts)) != kDone_Verb) {
1599 switch (verb) {
1600 case kMove_Verb:
1601#ifdef SK_CAN_USE_FLOAT
1602 SkDebugf(" path: moveTo [%g %g]\n",
1603 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1604#else
1605 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1606#endif
1607 break;
1608 case kLine_Verb:
1609#ifdef SK_CAN_USE_FLOAT
1610 SkDebugf(" path: lineTo [%g %g]\n",
1611 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1612#else
1613 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1614#endif
1615 break;
1616 case kQuad_Verb:
1617#ifdef SK_CAN_USE_FLOAT
1618 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1619 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1620 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1621#else
1622 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1623 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1624#endif
1625 break;
1626 case kCubic_Verb:
1627#ifdef SK_CAN_USE_FLOAT
1628 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1629 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1630 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1631 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1632#else
1633 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1634 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1635 pts[3].fX, pts[3].fY);
1636#endif
1637 break;
1638 case kClose_Verb:
1639 SkDebugf(" path: close\n");
1640 break;
1641 default:
1642 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1643 verb = kDone_Verb; // stop the loop
1644 break;
1645 }
1646 }
1647 SkDebugf("path: done %s\n", title ? title : "");
1648}
1649
reed@android.come522ca52009-11-23 20:10:41 +00001650void SkPath::dump() const {
1651 this->dump(false);
1652}
1653
1654#ifdef SK_DEBUG
1655void SkPath::validate() const {
1656 SkASSERT(this != NULL);
1657 SkASSERT((fFillType & ~3) == 0);
1658 fPts.validate();
1659 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001660
reed@android.come522ca52009-11-23 20:10:41 +00001661 if (!fBoundsIsDirty) {
1662 SkRect bounds;
1663 compute_pt_bounds(&bounds, fPts);
1664 if (fPts.count() <= 1) {
1665 // if we're empty, fBounds may be empty but translated, so we can't
1666 // necessarily compare to bounds directly
1667 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1668 // be [2, 2, 2, 2]
1669 SkASSERT(bounds.isEmpty());
1670 SkASSERT(fBounds.isEmpty());
1671 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001672 if (bounds.isEmpty()) {
1673 SkASSERT(fBounds.isEmpty());
1674 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001675 if (!fBounds.isEmpty()) {
1676 SkASSERT(fBounds.contains(bounds));
1677 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001678 }
reed@android.come522ca52009-11-23 20:10:41 +00001679 }
1680 }
reed@google.com10296cc2011-09-21 12:29:05 +00001681
1682 uint32_t mask = 0;
1683 for (int i = 0; i < fVerbs.count(); i++) {
1684 switch (fVerbs[i]) {
1685 case kLine_Verb:
1686 mask |= kLine_SegmentMask;
1687 break;
1688 case kQuad_Verb:
1689 mask |= kQuad_SegmentMask;
1690 break;
1691 case kCubic_Verb:
1692 mask |= kCubic_SegmentMask;
1693 }
1694 }
1695 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001696}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697#endif
reed@android.come522ca52009-11-23 20:10:41 +00001698
reed@google.com04863fa2011-05-15 04:08:24 +00001699///////////////////////////////////////////////////////////////////////////////
1700
reed@google.com0b7b9822011-05-16 12:29:27 +00001701static int sign(SkScalar x) { return x < 0; }
1702#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001703
reed@google.com04863fa2011-05-15 04:08:24 +00001704static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001705 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001706}
1707
1708// only valid for a single contour
1709struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001710 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001711 fSign = 0;
1712 // warnings
1713 fCurrPt.set(0, 0);
1714 fVec0.set(0, 0);
1715 fVec1.set(0, 0);
1716 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001717
1718 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001719 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001720 }
1721
1722 SkPath::Convexity getConvexity() const { return fConvexity; }
1723
1724 void addPt(const SkPoint& pt) {
1725 if (SkPath::kConcave_Convexity == fConvexity) {
1726 return;
1727 }
1728
1729 if (0 == fPtCount) {
1730 fCurrPt = pt;
1731 ++fPtCount;
1732 } else {
1733 SkVector vec = pt - fCurrPt;
1734 if (vec.fX || vec.fY) {
1735 fCurrPt = pt;
1736 if (++fPtCount == 2) {
1737 fFirstVec = fVec1 = vec;
1738 } else {
1739 SkASSERT(fPtCount > 2);
1740 this->addVec(vec);
1741 }
reed@google.com85b6e392011-05-15 20:25:17 +00001742
1743 int sx = sign(vec.fX);
1744 int sy = sign(vec.fY);
1745 fDx += (sx != fSx);
1746 fDy += (sy != fSy);
1747 fSx = sx;
1748 fSy = sy;
1749
1750 if (fDx > 3 || fDy > 3) {
1751 fConvexity = SkPath::kConcave_Convexity;
1752 }
reed@google.com04863fa2011-05-15 04:08:24 +00001753 }
1754 }
1755 }
1756
1757 void close() {
1758 if (fPtCount > 2) {
1759 this->addVec(fFirstVec);
1760 }
1761 }
1762
1763private:
1764 void addVec(const SkVector& vec) {
1765 SkASSERT(vec.fX || vec.fY);
1766 fVec0 = fVec1;
1767 fVec1 = vec;
1768 int sign = CrossProductSign(fVec0, fVec1);
1769 if (0 == fSign) {
1770 fSign = sign;
1771 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001772 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001773 fConvexity = SkPath::kConcave_Convexity;
1774 }
1775 }
1776 }
1777
1778 SkPoint fCurrPt;
1779 SkVector fVec0, fVec1, fFirstVec;
1780 int fPtCount; // non-degenerate points
1781 int fSign;
1782 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001783 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001784};
1785
1786SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1787 SkPoint pts[4];
1788 SkPath::Verb verb;
1789 SkPath::Iter iter(path, true);
1790
1791 int contourCount = 0;
1792 int count;
1793 Convexicator state;
1794
1795 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1796 switch (verb) {
1797 case kMove_Verb:
1798 if (++contourCount > 1) {
1799 return kConcave_Convexity;
1800 }
1801 pts[1] = pts[0];
1802 count = 1;
1803 break;
1804 case kLine_Verb: count = 1; break;
1805 case kQuad_Verb: count = 2; break;
1806 case kCubic_Verb: count = 3; break;
1807 case kClose_Verb:
1808 state.close();
1809 count = 0;
1810 break;
1811 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001812 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001813 return kConcave_Convexity;
1814 }
1815
1816 for (int i = 1; i <= count; i++) {
1817 state.addPt(pts[i]);
1818 }
1819 // early exit
1820 if (kConcave_Convexity == state.getConvexity()) {
1821 return kConcave_Convexity;
1822 }
1823 }
1824 return state.getConvexity();
1825}
reed@google.com69a99432012-01-10 18:00:10 +00001826
1827///////////////////////////////////////////////////////////////////////////////
1828
1829class ContourIter {
1830public:
1831 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1832
1833 bool done() const { return fDone; }
1834 // if !done() then these may be called
1835 int count() const { return fCurrPtCount; }
1836 const SkPoint* pts() const { return fCurrPt; }
1837 void next();
1838
1839private:
1840 int fCurrPtCount;
1841 const SkPoint* fCurrPt;
1842 const uint8_t* fCurrVerb;
1843 const uint8_t* fStopVerbs;
1844 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001845 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001846};
1847
1848ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1849 const SkTDArray<SkPoint>& pts) {
1850 fStopVerbs = verbs.begin() + verbs.count();
1851
1852 fDone = false;
1853 fCurrPt = pts.begin();
1854 fCurrVerb = verbs.begin();
1855 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001856 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001857 this->next();
1858}
1859
1860void ContourIter::next() {
1861 if (fCurrVerb >= fStopVerbs) {
1862 fDone = true;
1863 }
1864 if (fDone) {
1865 return;
1866 }
1867
1868 // skip pts of prev contour
1869 fCurrPt += fCurrPtCount;
1870
1871 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1872 int ptCount = 1; // moveTo
1873 const uint8_t* verbs = fCurrVerb;
1874
1875 for (++verbs; verbs < fStopVerbs; ++verbs) {
1876 switch (*verbs) {
1877 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001878 goto CONTOUR_END;
1879 case SkPath::kLine_Verb:
1880 ptCount += 1;
1881 break;
1882 case SkPath::kQuad_Verb:
1883 ptCount += 2;
1884 break;
1885 case SkPath::kCubic_Verb:
1886 ptCount += 3;
1887 break;
1888 default: // kClose_Verb, just keep going
1889 break;
1890 }
1891 }
1892CONTOUR_END:
1893 fCurrPtCount = ptCount;
1894 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001895 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001896}
1897
1898static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1899 return SkPoint::CrossProduct(p1 - p0, p2 - p0);
1900}
1901
1902static int find_max_y(const SkPoint pts[], int count) {
1903 SkASSERT(count > 0);
1904 SkScalar max = pts[0].fY;
1905 int maxIndex = 0;
1906 for (int i = 1; i < count; ++i) {
1907 if (pts[i].fY > max) {
1908 max = pts[i].fY;
1909 maxIndex = i;
1910 }
1911 }
1912 return maxIndex;
1913}
1914
reed@google.comcabaf1d2012-01-11 21:03:05 +00001915static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
1916 int i = index;
1917 for (;;) {
1918 i = (i + inc) % n;
1919 if (i == index) { // we wrapped around, so abort
1920 break;
1921 }
1922 if (pts[index] != pts[i]) { // found a different point, success!
1923 break;
1924 }
1925 }
1926 return i;
1927}
1928
reed@google.com69a99432012-01-10 18:00:10 +00001929bool SkPath::cheapComputeDirection(Direction* dir) const {
1930 // don't want to pay the cost for computing this if it
1931 // is unknown, so we don't call isConvex()
1932 const Convexity conv = this->getConvexityOrUnknown();
1933
1934 ContourIter iter(fVerbs, fPts);
1935
1936 for (; !iter.done(); iter.next()) {
1937 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00001938 if (n < 3) {
1939 continue;
1940 }
reed@google.com69a99432012-01-10 18:00:10 +00001941
reed@google.comcabaf1d2012-01-11 21:03:05 +00001942 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00001943 SkScalar cross = 0;
1944 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00001945 // we loop, skipping over degenerate or flat segments that will
1946 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00001947 for (int i = 0; i < n - 2; ++i) {
1948 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
1949 if (cross) {
1950 break;
1951 }
1952 }
1953 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00001954 int index = find_max_y(pts, n);
1955 // Find a next and prev index to use for the cross-product test,
1956 // but we try to find pts that form non-zero vectors from pts[index]
1957 //
1958 // Its possible that we can't find two non-degenerate vectors, so
1959 // we have to guard our search (e.g. all the pts could be in the
1960 // same place).
1961
1962 // we pass n - 1 instead of -1 so we don't foul up % operator by
1963 // passing it a negative LH argument.
1964 int prev = find_diff_pt(pts, index, n, n - 1);
1965 if (prev == index) {
1966 // completely degenerate, skip to next contour
1967 continue;
1968 }
1969 int next = find_diff_pt(pts, index, n, 1);
1970 SkASSERT(next != index);
1971 cross = cross_prod(pts[prev], pts[index], pts[next]);
reed@google.com188bfcf2012-01-17 18:26:38 +00001972 // if we get a zero, but the pts aren't on top of each other, then
1973 // we can just look at the direction
1974 if (0 == cross) {
1975 // construct the subtract so we get the correct Direction below
1976 cross = pts[index].fX - pts[next].fX;
1977 }
reed@google.com69a99432012-01-10 18:00:10 +00001978 }
1979 if (cross) {
1980 if (dir) {
1981 *dir = cross > 0 ? kCW_Direction : kCCW_Direction;
1982 }
1983 return true;
1984 }
1985 }
1986 return false; // unknown
1987}
1988