blob: 189caa5b6375ed84655e18728bfc30938cc7ed6e [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.orge42b1d52011-12-21 19:13:51 +0000210#if SK_OLD_EMPTY_PATH_BEHAVIOR
211 int count = fVerbs.count();
212 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
213#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000214 return 0 == fVerbs.count();
schenney@chromium.orge42b1d52011-12-21 19:13:51 +0000215#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216}
217
caryclark@google.comf1316942011-07-26 19:54:45 +0000218/*
219 Determines if path is a rect by keeping track of changes in direction
220 and looking for a loop either clockwise or counterclockwise.
221
222 The direction is computed such that:
223 0: vertical up
224 1: horizontal right
225 2: vertical down
226 3: horizontal left
227
228A rectangle cycles up/right/down/left or up/left/down/right.
229
230The test fails if:
231 The path is closed, and followed by a line.
232 A second move creates a new endpoint.
233 A diagonal line is parsed.
234 There's more than four changes of direction.
235 There's a discontinuity on the line (e.g., a move in the middle)
236 The line reverses direction.
237 The rectangle doesn't complete a cycle.
238 The path contains a quadratic or cubic.
239 The path contains fewer than four points.
240 The final point isn't equal to the first point.
241
242It's OK if the path has:
243 Several colinear line segments composing a rectangle side.
244 Single points on the rectangle side.
245
246The direction takes advantage of the corners found since opposite sides
247must travel in opposite directions.
248
249FIXME: Allow colinear quads and cubics to be treated like lines.
250FIXME: If the API passes fill-only, return true if the filled stroke
251 is a rectangle, though the caller failed to close the path.
252 */
253bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000255
caryclark@google.comf1316942011-07-26 19:54:45 +0000256 int corners = 0;
257 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000258 first.set(0, 0);
259 last.set(0, 0);
260 int firstDirection = 0;
261 int lastDirection = 0;
262 int nextDirection = 0;
263 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000264 bool autoClose = false;
265 const uint8_t* verbs = fVerbs.begin();
266 const uint8_t* verbStop = fVerbs.end();
267 const SkPoint* pts = fPts.begin();
268 while (verbs != verbStop) {
269 switch (*verbs++) {
270 case kClose_Verb:
271 pts = fPts.begin();
272 autoClose = true;
273 case kLine_Verb: {
274 SkScalar left = last.fX;
275 SkScalar top = last.fY;
276 SkScalar right = pts->fX;
277 SkScalar bottom = pts->fY;
278 ++pts;
279 if (left != right && top != bottom) {
280 return false; // diagonal
281 }
282 if (left == right && top == bottom) {
283 break; // single point on side OK
284 }
285 nextDirection = (left != right) << 0 |
286 (left < right || top < bottom) << 1;
287 if (0 == corners) {
288 firstDirection = nextDirection;
289 first = last;
290 last = pts[-1];
291 corners = 1;
292 closedOrMoved = false;
293 break;
294 }
295 if (closedOrMoved) {
296 return false; // closed followed by a line
297 }
298 closedOrMoved = autoClose;
299 if (lastDirection != nextDirection) {
300 if (++corners > 4) {
301 return false; // too many direction changes
302 }
303 }
304 last = pts[-1];
305 if (lastDirection == nextDirection) {
306 break; // colinear segment
307 }
308 // Possible values for corners are 2, 3, and 4.
309 // When corners == 3, nextDirection opposes firstDirection.
310 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000311 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000312 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
313 if ((directionCycle ^ turn) != nextDirection) {
314 return false; // direction didn't follow cycle
315 }
316 break;
317 }
318 case kQuad_Verb:
319 case kCubic_Verb:
320 return false; // quadratic, cubic not allowed
321 case kMove_Verb:
322 last = *pts++;
323 closedOrMoved = true;
324 break;
325 }
326 lastDirection = nextDirection;
327 }
328 // Success if 4 corners and first point equals last
329 bool result = 4 == corners && first == last;
330 if (result && rect) {
331 *rect = getBounds();
332 }
333 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334}
335
336int SkPath::getPoints(SkPoint copy[], int max) const {
337 SkDEBUGCODE(this->validate();)
338
339 SkASSERT(max >= 0);
340 int count = fPts.count();
341 if (copy && max > 0 && count > 0) {
342 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
343 }
344 return count;
345}
346
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000347SkPoint SkPath::getPoint(int index) const {
348 if ((unsigned)index < (unsigned)fPts.count()) {
349 return fPts[index];
350 }
351 return SkPoint::Make(0, 0);
352}
353
reed@google.com294dd7b2011-10-11 11:58:32 +0000354bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 SkDEBUGCODE(this->validate();)
356
reed@google.com294dd7b2011-10-11 11:58:32 +0000357 int count = fPts.count();
358 if (count > 0) {
359 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 *lastPt = fPts[count - 1];
361 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000362 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000364 if (lastPt) {
365 lastPt->set(0, 0);
366 }
367 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368}
369
370void SkPath::setLastPt(SkScalar x, SkScalar y) {
371 SkDEBUGCODE(this->validate();)
372
373 int count = fPts.count();
374 if (count == 0) {
375 this->moveTo(x, y);
376 } else {
377 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000378 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379 }
380}
381
reed@android.comd252db02009-04-01 18:31:44 +0000382void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000384 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385
reed@android.comd252db02009-04-01 18:31:44 +0000386 fBoundsIsDirty = false;
387 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388}
389
reed@google.com04863fa2011-05-15 04:08:24 +0000390void SkPath::setConvexity(Convexity c) {
391 if (fConvexity != c) {
392 fConvexity = c;
393 GEN_ID_INC;
394 }
395}
396
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397//////////////////////////////////////////////////////////////////////////////
398// Construction methods
399
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000400#define DIRTY_AFTER_EDIT \
401 do { \
402 fBoundsIsDirty = true; \
403 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000404 } while (0)
405
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406void SkPath::incReserve(U16CPU inc) {
407 SkDEBUGCODE(this->validate();)
408
409 fVerbs.setReserve(fVerbs.count() + inc);
410 fPts.setReserve(fPts.count() + inc);
411
412 SkDEBUGCODE(this->validate();)
413}
414
415void SkPath::moveTo(SkScalar x, SkScalar y) {
416 SkDEBUGCODE(this->validate();)
417
418 int vc = fVerbs.count();
419 SkPoint* pt;
420
reed@google.comd335d1d2012-01-12 18:17:11 +0000421 // remember our index
422 fLastMoveToIndex = fPts.count();
423
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000424#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
425 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
426 pt = &fPts[fPts.count() - 1];
427 } else {
428 pt = fPts.append();
429 *fVerbs.append() = kMove_Verb;
430 }
431#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000432 pt = fPts.append();
433 *fVerbs.append() = kMove_Verb;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000434#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435 pt->set(x, y);
436
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000437 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000438 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439}
440
441void SkPath::rMoveTo(SkScalar x, SkScalar y) {
442 SkPoint pt;
443 this->getLastPt(&pt);
444 this->moveTo(pt.fX + x, pt.fY + y);
445}
446
reed@google.comd335d1d2012-01-12 18:17:11 +0000447void SkPath::injectMoveToIfNeeded() {
448 if (fLastMoveToIndex < 0) {
449 SkScalar x, y;
450 if (fVerbs.count() == 0) {
451 x = y = 0;
452 } else {
453 const SkPoint& pt = fPts[~fLastMoveToIndex];
454 x = pt.fX;
455 y = pt.fY;
456 }
457 this->moveTo(x, y);
458 }
459}
460
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461void SkPath::lineTo(SkScalar x, SkScalar y) {
462 SkDEBUGCODE(this->validate();)
463
reed@google.comd335d1d2012-01-12 18:17:11 +0000464 this->injectMoveToIfNeeded();
465
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 fPts.append()->set(x, y);
467 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000468 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000470 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000471 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472}
473
474void SkPath::rLineTo(SkScalar x, SkScalar y) {
475 SkPoint pt;
476 this->getLastPt(&pt);
477 this->lineTo(pt.fX + x, pt.fY + y);
478}
479
480void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
481 SkDEBUGCODE(this->validate();)
482
reed@google.comd335d1d2012-01-12 18:17:11 +0000483 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484
485 SkPoint* pts = fPts.append(2);
486 pts[0].set(x1, y1);
487 pts[1].set(x2, y2);
488 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000489 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000491 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000492 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493}
494
495void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
496 SkPoint pt;
497 this->getLastPt(&pt);
498 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
499}
500
501void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
502 SkScalar x3, SkScalar y3) {
503 SkDEBUGCODE(this->validate();)
504
reed@google.comd335d1d2012-01-12 18:17:11 +0000505 this->injectMoveToIfNeeded();
506
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 SkPoint* pts = fPts.append(3);
508 pts[0].set(x1, y1);
509 pts[1].set(x2, y2);
510 pts[2].set(x3, y3);
511 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000512 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000513
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000514 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000515 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516}
517
518void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
519 SkScalar x3, SkScalar y3) {
520 SkPoint pt;
521 this->getLastPt(&pt);
522 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
523 pt.fX + x3, pt.fY + y3);
524}
525
526void SkPath::close() {
527 SkDEBUGCODE(this->validate();)
528
529 int count = fVerbs.count();
530 if (count > 0) {
531 switch (fVerbs[count - 1]) {
532 case kLine_Verb:
533 case kQuad_Verb:
534 case kCubic_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000535#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000536 case kMove_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000537#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000539 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540 break;
541 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000542 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000543 break;
544 }
545 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000546
547 // signal that we need a moveTo to follow us (unless we're done)
548#if 0
549 if (fLastMoveToIndex >= 0) {
550 fLastMoveToIndex = ~fLastMoveToIndex;
551 }
552#else
553 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
554#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555}
556
557///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000558
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559void SkPath::addRect(const SkRect& rect, Direction dir) {
560 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
561}
562
563void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
564 SkScalar bottom, Direction dir) {
565 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
566
567 this->incReserve(5);
568
569 this->moveTo(left, top);
570 if (dir == kCCW_Direction) {
571 this->lineTo(left, bottom);
572 this->lineTo(right, bottom);
573 this->lineTo(right, top);
574 } else {
575 this->lineTo(right, top);
576 this->lineTo(right, bottom);
577 this->lineTo(left, bottom);
578 }
579 this->close();
580}
581
582#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
583
584void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
585 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586 SkScalar w = rect.width();
587 SkScalar halfW = SkScalarHalf(w);
588 SkScalar h = rect.height();
589 SkScalar halfH = SkScalarHalf(h);
590
591 if (halfW <= 0 || halfH <= 0) {
592 return;
593 }
594
reed@google.comabf15c12011-01-18 20:35:51 +0000595 bool skip_hori = rx >= halfW;
596 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597
598 if (skip_hori && skip_vert) {
599 this->addOval(rect, dir);
600 return;
601 }
reed@google.comabf15c12011-01-18 20:35:51 +0000602
603 SkAutoPathBoundsUpdate apbu(this, rect);
604
reed@android.com8a1c16f2008-12-17 15:59:43 +0000605 if (skip_hori) {
606 rx = halfW;
607 } else if (skip_vert) {
608 ry = halfH;
609 }
610
611 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
612 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
613
614 this->incReserve(17);
615 this->moveTo(rect.fRight - rx, rect.fTop);
616 if (dir == kCCW_Direction) {
617 if (!skip_hori) {
618 this->lineTo(rect.fLeft + rx, rect.fTop); // top
619 }
620 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
621 rect.fLeft, rect.fTop + ry - sy,
622 rect.fLeft, rect.fTop + ry); // top-left
623 if (!skip_vert) {
624 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
625 }
626 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
627 rect.fLeft + rx - sx, rect.fBottom,
628 rect.fLeft + rx, rect.fBottom); // bot-left
629 if (!skip_hori) {
630 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
631 }
632 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
633 rect.fRight, rect.fBottom - ry + sy,
634 rect.fRight, rect.fBottom - ry); // bot-right
635 if (!skip_vert) {
636 this->lineTo(rect.fRight, rect.fTop + ry);
637 }
638 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
639 rect.fRight - rx + sx, rect.fTop,
640 rect.fRight - rx, rect.fTop); // top-right
641 } else {
642 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
643 rect.fRight, rect.fTop + ry - sy,
644 rect.fRight, rect.fTop + ry); // top-right
645 if (!skip_vert) {
646 this->lineTo(rect.fRight, rect.fBottom - ry);
647 }
648 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
649 rect.fRight - rx + sx, rect.fBottom,
650 rect.fRight - rx, rect.fBottom); // bot-right
651 if (!skip_hori) {
652 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
653 }
654 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
655 rect.fLeft, rect.fBottom - ry + sy,
656 rect.fLeft, rect.fBottom - ry); // bot-left
657 if (!skip_vert) {
658 this->lineTo(rect.fLeft, rect.fTop + ry); // left
659 }
660 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
661 rect.fLeft + rx - sx, rect.fTop,
662 rect.fLeft + rx, rect.fTop); // top-left
663 if (!skip_hori) {
664 this->lineTo(rect.fRight - rx, rect.fTop); // top
665 }
666 }
667 this->close();
668}
669
670static void add_corner_arc(SkPath* path, const SkRect& rect,
671 SkScalar rx, SkScalar ry, int startAngle,
672 SkPath::Direction dir, bool forceMoveTo) {
673 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
674 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000675
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676 SkRect r;
677 r.set(-rx, -ry, rx, ry);
678
679 switch (startAngle) {
680 case 0:
681 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
682 break;
683 case 90:
684 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
685 break;
686 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
687 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000688 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 }
reed@google.comabf15c12011-01-18 20:35:51 +0000690
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 SkScalar start = SkIntToScalar(startAngle);
692 SkScalar sweep = SkIntToScalar(90);
693 if (SkPath::kCCW_Direction == dir) {
694 start += sweep;
695 sweep = -sweep;
696 }
reed@google.comabf15c12011-01-18 20:35:51 +0000697
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 path->arcTo(r, start, sweep, forceMoveTo);
699}
700
701void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
702 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000703 // abort before we invoke SkAutoPathBoundsUpdate()
704 if (rect.isEmpty()) {
705 return;
706 }
707
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 SkAutoPathBoundsUpdate apbu(this, rect);
709
710 if (kCW_Direction == dir) {
711 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
712 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
713 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
714 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
715 } else {
716 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
717 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
718 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
719 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
720 }
721 this->close();
722}
723
724void SkPath::addOval(const SkRect& oval, Direction dir) {
725 SkAutoPathBoundsUpdate apbu(this, oval);
726
727 SkScalar cx = oval.centerX();
728 SkScalar cy = oval.centerY();
729 SkScalar rx = SkScalarHalf(oval.width());
730 SkScalar ry = SkScalarHalf(oval.height());
731#if 0 // these seem faster than using quads (1/2 the number of edges)
732 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
733 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
734
735 this->incReserve(13);
736 this->moveTo(cx + rx, cy);
737 if (dir == kCCW_Direction) {
738 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
739 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
740 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
741 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
742 } else {
743 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
744 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
745 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
746 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
747 }
748#else
749 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
750 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
751 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
752 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
753
754 /*
755 To handle imprecision in computing the center and radii, we revert to
756 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
757 to ensure that we don't exceed the oval's bounds *ever*, since we want
758 to use oval for our fast-bounds, rather than have to recompute it.
759 */
760 const SkScalar L = oval.fLeft; // cx - rx
761 const SkScalar T = oval.fTop; // cy - ry
762 const SkScalar R = oval.fRight; // cx + rx
763 const SkScalar B = oval.fBottom; // cy + ry
764
765 this->incReserve(17); // 8 quads + close
766 this->moveTo(R, cy);
767 if (dir == kCCW_Direction) {
768 this->quadTo( R, cy - sy, cx + mx, cy - my);
769 this->quadTo(cx + sx, T, cx , T);
770 this->quadTo(cx - sx, T, cx - mx, cy - my);
771 this->quadTo( L, cy - sy, L, cy );
772 this->quadTo( L, cy + sy, cx - mx, cy + my);
773 this->quadTo(cx - sx, B, cx , B);
774 this->quadTo(cx + sx, B, cx + mx, cy + my);
775 this->quadTo( R, cy + sy, R, cy );
776 } else {
777 this->quadTo( R, cy + sy, cx + mx, cy + my);
778 this->quadTo(cx + sx, B, cx , B);
779 this->quadTo(cx - sx, B, cx - mx, cy + my);
780 this->quadTo( L, cy + sy, L, cy );
781 this->quadTo( L, cy - sy, cx - mx, cy - my);
782 this->quadTo(cx - sx, T, cx , T);
783 this->quadTo(cx + sx, T, cx + mx, cy - my);
784 this->quadTo( R, cy - sy, R, cy );
785 }
786#endif
787 this->close();
788}
789
790void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
791 if (r > 0) {
792 SkRect rect;
793 rect.set(x - r, y - r, x + r, y + r);
794 this->addOval(rect, dir);
795 }
796}
797
798#include "SkGeometry.h"
799
800static int build_arc_points(const SkRect& oval, SkScalar startAngle,
801 SkScalar sweepAngle,
802 SkPoint pts[kSkBuildQuadArcStorage]) {
803 SkVector start, stop;
804
805 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
806 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
807 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000808
809 /* If the sweep angle is nearly (but less than) 360, then due to precision
810 loss in radians-conversion and/or sin/cos, we may end up with coincident
811 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
812 of drawing a nearly complete circle (good).
813 e.g. canvas.drawArc(0, 359.99, ...)
814 -vs- canvas.drawArc(0, 359.9, ...)
815 We try to detect this edge case, and tweak the stop vector
816 */
817 if (start == stop) {
818 SkScalar sw = SkScalarAbs(sweepAngle);
819 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
820 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
821 // make a guess at a tiny angle (in radians) to tweak by
822 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
823 // not sure how much will be enough, so we use a loop
824 do {
825 stopRad -= deltaRad;
826 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
827 } while (start == stop);
828 }
829 }
830
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000832
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
834 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000835
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 return SkBuildQuadArc(start, stop,
837 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
838 &matrix, pts);
839}
840
841void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
842 bool forceMoveTo) {
843 if (oval.width() < 0 || oval.height() < 0) {
844 return;
845 }
846
847 SkPoint pts[kSkBuildQuadArcStorage];
848 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
849 SkASSERT((count & 1) == 1);
850
851 if (fVerbs.count() == 0) {
852 forceMoveTo = true;
853 }
854 this->incReserve(count);
855 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
856 for (int i = 1; i < count; i += 2) {
857 this->quadTo(pts[i], pts[i+1]);
858 }
859}
860
861void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
862 SkScalar sweepAngle) {
863 if (oval.isEmpty() || 0 == sweepAngle) {
864 return;
865 }
866
867 const SkScalar kFullCircleAngle = SkIntToScalar(360);
868
869 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
870 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
871 return;
872 }
873
874 SkPoint pts[kSkBuildQuadArcStorage];
875 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
876
877 this->incReserve(count);
878 this->moveTo(pts[0]);
879 for (int i = 1; i < count; i += 2) {
880 this->quadTo(pts[i], pts[i+1]);
881 }
882}
883
884/*
885 Need to handle the case when the angle is sharp, and our computed end-points
886 for the arc go behind pt1 and/or p2...
887*/
888void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
889 SkScalar radius) {
890 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000891
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 // need to know our prev pt so we can construct tangent vectors
893 {
894 SkPoint start;
895 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000896 // Handle degenerate cases by adding a line to the first point and
897 // bailing out.
898 if ((x1 == start.fX && y1 == start.fY) ||
899 (x1 == x2 && y1 == y2) ||
900 radius == 0) {
901 this->lineTo(x1, y1);
902 return;
903 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 before.setNormalize(x1 - start.fX, y1 - start.fY);
905 after.setNormalize(x2 - x1, y2 - y1);
906 }
reed@google.comabf15c12011-01-18 20:35:51 +0000907
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 SkScalar cosh = SkPoint::DotProduct(before, after);
909 SkScalar sinh = SkPoint::CrossProduct(before, after);
910
911 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000912 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 return;
914 }
reed@google.comabf15c12011-01-18 20:35:51 +0000915
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
917 if (dist < 0) {
918 dist = -dist;
919 }
920
921 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
922 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
923 SkRotationDirection arcDir;
924
925 // now turn before/after into normals
926 if (sinh > 0) {
927 before.rotateCCW();
928 after.rotateCCW();
929 arcDir = kCW_SkRotationDirection;
930 } else {
931 before.rotateCW();
932 after.rotateCW();
933 arcDir = kCCW_SkRotationDirection;
934 }
935
936 SkMatrix matrix;
937 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000938
reed@android.com8a1c16f2008-12-17 15:59:43 +0000939 matrix.setScale(radius, radius);
940 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
941 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000942
reed@android.com8a1c16f2008-12-17 15:59:43 +0000943 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000944
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945 this->incReserve(count);
946 // [xx,yy] == pts[0]
947 this->lineTo(xx, yy);
948 for (int i = 1; i < count; i += 2) {
949 this->quadTo(pts[i], pts[i+1]);
950 }
951}
952
953///////////////////////////////////////////////////////////////////////////////
954
955void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
956 SkMatrix matrix;
957
958 matrix.setTranslate(dx, dy);
959 this->addPath(path, matrix);
960}
961
962void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
963 this->incReserve(path.fPts.count());
964
schenney@chromium.org6630d8d2012-01-04 21:05:51 +0000965 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966 SkPoint pts[4];
967 Verb verb;
968
969 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
970
971 while ((verb = iter.next(pts)) != kDone_Verb) {
972 switch (verb) {
973 case kMove_Verb:
974 proc(matrix, &pts[0], &pts[0], 1);
975 this->moveTo(pts[0]);
976 break;
977 case kLine_Verb:
978 proc(matrix, &pts[1], &pts[1], 1);
979 this->lineTo(pts[1]);
980 break;
981 case kQuad_Verb:
982 proc(matrix, &pts[1], &pts[1], 2);
983 this->quadTo(pts[1], pts[2]);
984 break;
985 case kCubic_Verb:
986 proc(matrix, &pts[1], &pts[1], 3);
987 this->cubicTo(pts[1], pts[2], pts[3]);
988 break;
989 case kClose_Verb:
990 this->close();
991 break;
992 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000993 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994 }
995 }
996}
997
998///////////////////////////////////////////////////////////////////////////////
999
1000static const uint8_t gPtsInVerb[] = {
1001 1, // kMove
1002 1, // kLine
1003 2, // kQuad
1004 3, // kCubic
1005 0, // kClose
1006 0 // kDone
1007};
1008
1009// ignore the initial moveto, and stop when the 1st contour ends
1010void SkPath::pathTo(const SkPath& path) {
1011 int i, vcount = path.fVerbs.count();
1012 if (vcount == 0) {
1013 return;
1014 }
1015
1016 this->incReserve(vcount);
1017
1018 const uint8_t* verbs = path.fVerbs.begin();
1019 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1020
1021 SkASSERT(verbs[0] == kMove_Verb);
1022 for (i = 1; i < vcount; i++) {
1023 switch (verbs[i]) {
1024 case kLine_Verb:
1025 this->lineTo(pts[0].fX, pts[0].fY);
1026 break;
1027 case kQuad_Verb:
1028 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1029 break;
1030 case kCubic_Verb:
1031 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1032 pts[2].fX, pts[2].fY);
1033 break;
1034 case kClose_Verb:
1035 return;
1036 }
1037 pts += gPtsInVerb[verbs[i]];
1038 }
1039}
1040
1041// ignore the last point of the 1st contour
1042void SkPath::reversePathTo(const SkPath& path) {
1043 int i, vcount = path.fVerbs.count();
1044 if (vcount == 0) {
1045 return;
1046 }
1047
1048 this->incReserve(vcount);
1049
1050 const uint8_t* verbs = path.fVerbs.begin();
1051 const SkPoint* pts = path.fPts.begin();
1052
1053 SkASSERT(verbs[0] == kMove_Verb);
1054 for (i = 1; i < vcount; i++) {
1055 int n = gPtsInVerb[verbs[i]];
1056 if (n == 0) {
1057 break;
1058 }
1059 pts += n;
1060 }
1061
1062 while (--i > 0) {
1063 switch (verbs[i]) {
1064 case kLine_Verb:
1065 this->lineTo(pts[-1].fX, pts[-1].fY);
1066 break;
1067 case kQuad_Verb:
1068 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1069 break;
1070 case kCubic_Verb:
1071 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1072 pts[-3].fX, pts[-3].fY);
1073 break;
1074 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001075 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 break;
1077 }
1078 pts -= gPtsInVerb[verbs[i]];
1079 }
1080}
1081
reed@google.com63d73742012-01-10 15:33:12 +00001082void SkPath::reverseAddPath(const SkPath& src) {
1083 this->incReserve(src.fPts.count());
1084
1085 const SkPoint* startPts = src.fPts.begin();
1086 const SkPoint* pts = src.fPts.end();
1087 const uint8_t* startVerbs = src.fVerbs.begin();
1088 const uint8_t* verbs = src.fVerbs.end();
1089
1090 bool needMove = true;
1091 bool needClose = false;
1092 while (verbs > startVerbs) {
1093 uint8_t v = *--verbs;
1094 int n = gPtsInVerb[v];
1095
1096 if (needMove) {
1097 --pts;
1098 this->moveTo(pts->fX, pts->fY);
1099 needMove = false;
1100 }
1101 pts -= n;
1102 switch (v) {
1103 case kMove_Verb:
1104 if (needClose) {
1105 this->close();
1106 needClose = false;
1107 }
1108 needMove = true;
1109 pts += 1; // so we see the point in "if (needMove)" above
1110 break;
1111 case kLine_Verb:
1112 this->lineTo(pts[0]);
1113 break;
1114 case kQuad_Verb:
1115 this->quadTo(pts[1], pts[0]);
1116 break;
1117 case kCubic_Verb:
1118 this->cubicTo(pts[2], pts[1], pts[0]);
1119 break;
1120 case kClose_Verb:
1121 needClose = true;
1122 break;
1123 default:
1124 SkASSERT(!"unexpected verb");
1125 }
1126 }
1127}
1128
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129///////////////////////////////////////////////////////////////////////////////
1130
1131void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1132 SkMatrix matrix;
1133
1134 matrix.setTranslate(dx, dy);
1135 this->transform(matrix, dst);
1136}
1137
1138#include "SkGeometry.h"
1139
1140static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1141 int level = 2) {
1142 if (--level >= 0) {
1143 SkPoint tmp[5];
1144
1145 SkChopQuadAtHalf(pts, tmp);
1146 subdivide_quad_to(path, &tmp[0], level);
1147 subdivide_quad_to(path, &tmp[2], level);
1148 } else {
1149 path->quadTo(pts[1], pts[2]);
1150 }
1151}
1152
1153static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1154 int level = 2) {
1155 if (--level >= 0) {
1156 SkPoint tmp[7];
1157
1158 SkChopCubicAtHalf(pts, tmp);
1159 subdivide_cubic_to(path, &tmp[0], level);
1160 subdivide_cubic_to(path, &tmp[3], level);
1161 } else {
1162 path->cubicTo(pts[1], pts[2], pts[3]);
1163 }
1164}
1165
1166void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1167 SkDEBUGCODE(this->validate();)
1168 if (dst == NULL) {
1169 dst = (SkPath*)this;
1170 }
1171
tomhudson@google.com8d430182011-06-06 19:11:19 +00001172 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkPath tmp;
1174 tmp.fFillType = fFillType;
1175
1176 SkPath::Iter iter(*this, false);
1177 SkPoint pts[4];
1178 SkPath::Verb verb;
1179
1180 while ((verb = iter.next(pts)) != kDone_Verb) {
1181 switch (verb) {
1182 case kMove_Verb:
1183 tmp.moveTo(pts[0]);
1184 break;
1185 case kLine_Verb:
1186 tmp.lineTo(pts[1]);
1187 break;
1188 case kQuad_Verb:
1189 subdivide_quad_to(&tmp, pts);
1190 break;
1191 case kCubic_Verb:
1192 subdivide_cubic_to(&tmp, pts);
1193 break;
1194 case kClose_Verb:
1195 tmp.close();
1196 break;
1197 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001198 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 break;
1200 }
1201 }
1202
1203 dst->swap(tmp);
1204 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1205 } else {
1206 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001207 // fBoundsIsDirty before we set it
1208 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001210 matrix.mapRect(&dst->fBounds, fBounds);
1211 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001213 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001214 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 }
1216
1217 if (this != dst) {
1218 dst->fVerbs = fVerbs;
1219 dst->fPts.setCount(fPts.count());
1220 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001221 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001222 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 }
1224 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1225 SkDEBUGCODE(dst->validate();)
1226 }
1227}
1228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229///////////////////////////////////////////////////////////////////////////////
1230///////////////////////////////////////////////////////////////////////////////
1231
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001232enum SegmentState {
1233 kAfterClose_SegmentState, // We will need a move next, but we have a
1234 // previous close pt to use for the new move.
1235 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1236 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1237 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238};
1239
1240SkPath::Iter::Iter() {
1241#ifdef SK_DEBUG
1242 fPts = NULL;
1243 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001244 fForceClose = fCloseLine = false;
1245 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246#endif
1247 // need to init enough to make next() harmlessly return kDone_Verb
1248 fVerbs = NULL;
1249 fVerbStop = NULL;
1250 fNeedClose = false;
1251}
1252
1253SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1254 this->setPath(path, forceClose);
1255}
1256
1257void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1258 fPts = path.fPts.begin();
1259 fVerbs = path.fVerbs.begin();
1260 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001261 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001262 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263 fForceClose = SkToU8(forceClose);
1264 fNeedClose = false;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001265 fSegmentState = kAfterClose_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266}
1267
1268bool SkPath::Iter::isClosedContour() const {
1269 if (fVerbs == NULL || fVerbs == fVerbStop) {
1270 return false;
1271 }
1272 if (fForceClose) {
1273 return true;
1274 }
1275
1276 const uint8_t* verbs = fVerbs;
1277 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 if (kMove_Verb == *verbs) {
1280 verbs += 1; // skip the initial moveto
1281 }
1282
1283 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001284 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285 if (kMove_Verb == v) {
1286 break;
1287 }
1288 if (kClose_Verb == v) {
1289 return true;
1290 }
1291 }
1292 return false;
1293}
1294
1295SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1296 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001297 // A special case: if both points are NaN, SkPoint::operation== returns
1298 // false, but the iterator expects that they are treated as the same.
1299 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001300 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1301 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001302 return kClose_Verb;
1303 }
1304
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 if (pts) {
1306 pts[0] = fLastPt;
1307 pts[1] = fMoveTo;
1308 }
1309 fLastPt = fMoveTo;
1310 fCloseLine = true;
1311 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001312 } else {
1313 pts[0] = fMoveTo;
1314 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316}
1317
1318bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001319 if (fSegmentState == kAfterClose_SegmentState) {
1320 // We have closed a curve and have a primitive, so we need a move.
1321 // Set the first return pt to the most recent move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 if (pts) {
1323 *pts = fMoveTo;
1324 }
1325 fNeedClose = fForceClose;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001326 fSegmentState = kAfterMove_SegmentState;
1327 fVerbs -= 1; // Step back to see the primitive again
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328 return true;
1329 }
1330
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001331 if (fSegmentState == kAfterMove_SegmentState) {
1332 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 if (pts) {
1334 *pts = fMoveTo;
1335 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001336 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001338 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1339 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 if (pts) {
1341 *pts = fPts[-1];
1342 }
1343 }
1344 return false;
1345}
1346
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001347void SkPath::Iter::consumeDegenerateSegments() {
1348 // We need to step over anything that will not move the current draw point
1349 // forward before the next move is seen
1350 const uint8_t* lastMoveVerb = 0;
1351 const SkPoint* lastMovePt = 0;
1352 SkPoint lastPt = fLastPt;
1353 while (fVerbs != fVerbStop) {
1354 unsigned verb = *fVerbs;
1355 switch (verb) {
1356 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001357 // Keep a record of this most recent move
1358 lastMoveVerb = fVerbs;
1359 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001360 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001361 fVerbs++;
1362 fPts++;
1363 break;
1364
1365 case kClose_Verb:
1366 // A close when we are in a segment is always valid
1367 if (fSegmentState == kAfterPrimitive_SegmentState) {
1368 return;
1369 }
1370 // A close at any other time must be ignored
1371 fVerbs++;
1372 break;
1373
1374 case kLine_Verb:
1375 if (!IsLineDegenerate(lastPt, fPts[0])) {
1376 if (lastMoveVerb) {
1377 fVerbs = lastMoveVerb;
1378 fPts = lastMovePt;
1379 return;
1380 }
1381 return;
1382 }
1383 // Ignore this line and continue
1384 fVerbs++;
1385 fPts++;
1386 break;
1387
1388 case kQuad_Verb:
1389 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1390 if (lastMoveVerb) {
1391 fVerbs = lastMoveVerb;
1392 fPts = lastMovePt;
1393 return;
1394 }
1395 return;
1396 }
1397 // Ignore this line and continue
1398 fVerbs++;
1399 fPts += 2;
1400 break;
1401
1402 case kCubic_Verb:
1403 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1404 if (lastMoveVerb) {
1405 fVerbs = lastMoveVerb;
1406 fPts = lastMovePt;
1407 return;
1408 }
1409 return;
1410 }
1411 // Ignore this line and continue
1412 fVerbs++;
1413 fPts += 3;
1414 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001415
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001416 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001417 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001418 }
1419 }
1420}
1421
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001423#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001424 this->consumeDegenerateSegments();
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001425#endif
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001426
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001428 // Close the curve if requested and if there is some curve to close
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001429#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1430 if (fNeedClose) {
1431#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001432 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001433#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 if (kLine_Verb == this->autoClose(pts)) {
1435 return kLine_Verb;
1436 }
1437 fNeedClose = false;
1438 return kClose_Verb;
1439 }
1440 return kDone_Verb;
1441 }
1442
1443 unsigned verb = *fVerbs++;
1444 const SkPoint* srcPts = fPts;
1445
1446 switch (verb) {
1447 case kMove_Verb:
1448 if (fNeedClose) {
1449 fVerbs -= 1;
1450 verb = this->autoClose(pts);
1451 if (verb == kClose_Verb) {
1452 fNeedClose = false;
1453 }
1454 return (Verb)verb;
1455 }
1456 if (fVerbs == fVerbStop) { // might be a trailing moveto
1457 return kDone_Verb;
1458 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001459 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 if (pts) {
1461 pts[0] = *srcPts;
1462 }
1463 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001464 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001465#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001466 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001467#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 fNeedClose = fForceClose;
1469 break;
1470 case kLine_Verb:
1471 if (this->cons_moveTo(pts)) {
1472 return kMove_Verb;
1473 }
1474 if (pts) {
1475 pts[1] = srcPts[0];
1476 }
1477 fLastPt = srcPts[0];
1478 fCloseLine = false;
1479 srcPts += 1;
1480 break;
1481 case kQuad_Verb:
1482 if (this->cons_moveTo(pts)) {
1483 return kMove_Verb;
1484 }
1485 if (pts) {
1486 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1487 }
1488 fLastPt = srcPts[1];
1489 srcPts += 2;
1490 break;
1491 case kCubic_Verb:
1492 if (this->cons_moveTo(pts)) {
1493 return kMove_Verb;
1494 }
1495 if (pts) {
1496 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1497 }
1498 fLastPt = srcPts[2];
1499 srcPts += 3;
1500 break;
1501 case kClose_Verb:
1502 verb = this->autoClose(pts);
1503 if (verb == kLine_Verb) {
1504 fVerbs -= 1;
1505 } else {
1506 fNeedClose = false;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001507#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001508 fSegmentState = kAfterClose_SegmentState;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001509#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001511#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1512 fSegmentState = kAfterClose_SegmentState;
1513#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001514 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001515#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 break;
1517 }
1518 fPts = srcPts;
1519 return (Verb)verb;
1520}
1521
1522///////////////////////////////////////////////////////////////////////////////
1523
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001524SkPath::RawIter::RawIter() {
1525#ifdef SK_DEBUG
1526 fPts = NULL;
1527 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1528#endif
1529 // need to init enough to make next() harmlessly return kDone_Verb
1530 fVerbs = NULL;
1531 fVerbStop = NULL;
1532}
1533
1534SkPath::RawIter::RawIter(const SkPath& path) {
1535 this->setPath(path);
1536}
1537
1538void SkPath::RawIter::setPath(const SkPath& path) {
1539 fPts = path.fPts.begin();
1540 fVerbs = path.fVerbs.begin();
1541 fVerbStop = path.fVerbs.end();
1542 fMoveTo.fX = fMoveTo.fY = 0;
1543 fLastPt.fX = fLastPt.fY = 0;
1544}
1545
1546SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1547 if (fVerbs == fVerbStop) {
1548 return kDone_Verb;
1549 }
1550
1551 unsigned verb = *fVerbs++;
1552 const SkPoint* srcPts = fPts;
1553
1554 switch (verb) {
1555 case kMove_Verb:
1556 if (pts) {
1557 pts[0] = *srcPts;
1558 }
1559 fMoveTo = srcPts[0];
1560 fLastPt = fMoveTo;
1561 srcPts += 1;
1562 break;
1563 case kLine_Verb:
1564 if (pts) {
1565 pts[0] = fLastPt;
1566 pts[1] = srcPts[0];
1567 }
1568 fLastPt = srcPts[0];
1569 srcPts += 1;
1570 break;
1571 case kQuad_Verb:
1572 if (pts) {
1573 pts[0] = fLastPt;
1574 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1575 }
1576 fLastPt = srcPts[1];
1577 srcPts += 2;
1578 break;
1579 case kCubic_Verb:
1580 if (pts) {
1581 pts[0] = fLastPt;
1582 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1583 }
1584 fLastPt = srcPts[2];
1585 srcPts += 3;
1586 break;
1587 case kClose_Verb:
1588 fLastPt = fMoveTo;
1589 if (pts) {
1590 pts[0] = fMoveTo;
1591 }
1592 break;
1593 }
1594 fPts = srcPts;
1595 return (Verb)verb;
1596}
1597
1598///////////////////////////////////////////////////////////////////////////////
1599
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600/*
1601 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1602*/
1603
reed@google.com73945652011-04-25 19:04:27 +00001604void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 SkDEBUGCODE(this->validate();)
1606
1607 buffer.write32(fPts.count());
1608 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001609 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1611 buffer.writePad(fVerbs.begin(), fVerbs.count());
1612}
1613
reed@google.com73945652011-04-25 19:04:27 +00001614void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 fPts.setCount(buffer.readS32());
1616 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001617 uint32_t packed = buffer.readS32();
1618 fFillType = packed >> 8;
1619 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1621 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001622
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001623 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001624 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625
1626 SkDEBUGCODE(this->validate();)
1627}
1628
1629///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631void SkPath::dump(bool forceClose, const char title[]) const {
1632 Iter iter(*this, forceClose);
1633 SkPoint pts[4];
1634 Verb verb;
1635
1636 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1637 title ? title : "");
1638
1639 while ((verb = iter.next(pts)) != kDone_Verb) {
1640 switch (verb) {
1641 case kMove_Verb:
1642#ifdef SK_CAN_USE_FLOAT
1643 SkDebugf(" path: moveTo [%g %g]\n",
1644 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1645#else
1646 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1647#endif
1648 break;
1649 case kLine_Verb:
1650#ifdef SK_CAN_USE_FLOAT
1651 SkDebugf(" path: lineTo [%g %g]\n",
1652 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1653#else
1654 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1655#endif
1656 break;
1657 case kQuad_Verb:
1658#ifdef SK_CAN_USE_FLOAT
1659 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1660 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1661 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1662#else
1663 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1664 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1665#endif
1666 break;
1667 case kCubic_Verb:
1668#ifdef SK_CAN_USE_FLOAT
1669 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1670 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1671 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1672 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1673#else
1674 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1675 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1676 pts[3].fX, pts[3].fY);
1677#endif
1678 break;
1679 case kClose_Verb:
1680 SkDebugf(" path: close\n");
1681 break;
1682 default:
1683 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1684 verb = kDone_Verb; // stop the loop
1685 break;
1686 }
1687 }
1688 SkDebugf("path: done %s\n", title ? title : "");
1689}
1690
reed@android.come522ca52009-11-23 20:10:41 +00001691void SkPath::dump() const {
1692 this->dump(false);
1693}
1694
1695#ifdef SK_DEBUG
1696void SkPath::validate() const {
1697 SkASSERT(this != NULL);
1698 SkASSERT((fFillType & ~3) == 0);
1699 fPts.validate();
1700 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001701
reed@android.come522ca52009-11-23 20:10:41 +00001702 if (!fBoundsIsDirty) {
1703 SkRect bounds;
1704 compute_pt_bounds(&bounds, fPts);
1705 if (fPts.count() <= 1) {
1706 // if we're empty, fBounds may be empty but translated, so we can't
1707 // necessarily compare to bounds directly
1708 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1709 // be [2, 2, 2, 2]
1710 SkASSERT(bounds.isEmpty());
1711 SkASSERT(fBounds.isEmpty());
1712 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001713 if (bounds.isEmpty()) {
1714 SkASSERT(fBounds.isEmpty());
1715 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001716 if (!fBounds.isEmpty()) {
1717 SkASSERT(fBounds.contains(bounds));
1718 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001719 }
reed@android.come522ca52009-11-23 20:10:41 +00001720 }
1721 }
reed@google.com10296cc2011-09-21 12:29:05 +00001722
1723 uint32_t mask = 0;
1724 for (int i = 0; i < fVerbs.count(); i++) {
1725 switch (fVerbs[i]) {
1726 case kLine_Verb:
1727 mask |= kLine_SegmentMask;
1728 break;
1729 case kQuad_Verb:
1730 mask |= kQuad_SegmentMask;
1731 break;
1732 case kCubic_Verb:
1733 mask |= kCubic_SegmentMask;
1734 }
1735 }
1736 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001737}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738#endif
reed@android.come522ca52009-11-23 20:10:41 +00001739
reed@google.com04863fa2011-05-15 04:08:24 +00001740///////////////////////////////////////////////////////////////////////////////
1741
reed@google.com0b7b9822011-05-16 12:29:27 +00001742static int sign(SkScalar x) { return x < 0; }
1743#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001744
reed@google.com04863fa2011-05-15 04:08:24 +00001745static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001746 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001747}
1748
1749// only valid for a single contour
1750struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001751 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001752 fSign = 0;
1753 // warnings
1754 fCurrPt.set(0, 0);
1755 fVec0.set(0, 0);
1756 fVec1.set(0, 0);
1757 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001758
1759 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001760 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001761 }
1762
1763 SkPath::Convexity getConvexity() const { return fConvexity; }
1764
1765 void addPt(const SkPoint& pt) {
1766 if (SkPath::kConcave_Convexity == fConvexity) {
1767 return;
1768 }
1769
1770 if (0 == fPtCount) {
1771 fCurrPt = pt;
1772 ++fPtCount;
1773 } else {
1774 SkVector vec = pt - fCurrPt;
1775 if (vec.fX || vec.fY) {
1776 fCurrPt = pt;
1777 if (++fPtCount == 2) {
1778 fFirstVec = fVec1 = vec;
1779 } else {
1780 SkASSERT(fPtCount > 2);
1781 this->addVec(vec);
1782 }
reed@google.com85b6e392011-05-15 20:25:17 +00001783
1784 int sx = sign(vec.fX);
1785 int sy = sign(vec.fY);
1786 fDx += (sx != fSx);
1787 fDy += (sy != fSy);
1788 fSx = sx;
1789 fSy = sy;
1790
1791 if (fDx > 3 || fDy > 3) {
1792 fConvexity = SkPath::kConcave_Convexity;
1793 }
reed@google.com04863fa2011-05-15 04:08:24 +00001794 }
1795 }
1796 }
1797
1798 void close() {
1799 if (fPtCount > 2) {
1800 this->addVec(fFirstVec);
1801 }
1802 }
1803
1804private:
1805 void addVec(const SkVector& vec) {
1806 SkASSERT(vec.fX || vec.fY);
1807 fVec0 = fVec1;
1808 fVec1 = vec;
1809 int sign = CrossProductSign(fVec0, fVec1);
1810 if (0 == fSign) {
1811 fSign = sign;
1812 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001813 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001814 fConvexity = SkPath::kConcave_Convexity;
1815 }
1816 }
1817 }
1818
1819 SkPoint fCurrPt;
1820 SkVector fVec0, fVec1, fFirstVec;
1821 int fPtCount; // non-degenerate points
1822 int fSign;
1823 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001824 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001825};
1826
1827SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1828 SkPoint pts[4];
1829 SkPath::Verb verb;
1830 SkPath::Iter iter(path, true);
1831
1832 int contourCount = 0;
1833 int count;
1834 Convexicator state;
1835
1836 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1837 switch (verb) {
1838 case kMove_Verb:
1839 if (++contourCount > 1) {
1840 return kConcave_Convexity;
1841 }
1842 pts[1] = pts[0];
1843 count = 1;
1844 break;
1845 case kLine_Verb: count = 1; break;
1846 case kQuad_Verb: count = 2; break;
1847 case kCubic_Verb: count = 3; break;
1848 case kClose_Verb:
1849 state.close();
1850 count = 0;
1851 break;
1852 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001853 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001854 return kConcave_Convexity;
1855 }
1856
1857 for (int i = 1; i <= count; i++) {
1858 state.addPt(pts[i]);
1859 }
1860 // early exit
1861 if (kConcave_Convexity == state.getConvexity()) {
1862 return kConcave_Convexity;
1863 }
1864 }
1865 return state.getConvexity();
1866}
reed@google.com69a99432012-01-10 18:00:10 +00001867
1868///////////////////////////////////////////////////////////////////////////////
1869
1870class ContourIter {
1871public:
1872 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1873
1874 bool done() const { return fDone; }
1875 // if !done() then these may be called
1876 int count() const { return fCurrPtCount; }
1877 const SkPoint* pts() const { return fCurrPt; }
1878 void next();
1879
1880private:
1881 int fCurrPtCount;
1882 const SkPoint* fCurrPt;
1883 const uint8_t* fCurrVerb;
1884 const uint8_t* fStopVerbs;
1885 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001886 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001887};
1888
1889ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1890 const SkTDArray<SkPoint>& pts) {
1891 fStopVerbs = verbs.begin() + verbs.count();
1892
1893 fDone = false;
1894 fCurrPt = pts.begin();
1895 fCurrVerb = verbs.begin();
1896 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001897 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001898 this->next();
1899}
1900
1901void ContourIter::next() {
1902 if (fCurrVerb >= fStopVerbs) {
1903 fDone = true;
1904 }
1905 if (fDone) {
1906 return;
1907 }
1908
1909 // skip pts of prev contour
1910 fCurrPt += fCurrPtCount;
1911
1912 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1913 int ptCount = 1; // moveTo
1914 const uint8_t* verbs = fCurrVerb;
1915
1916 for (++verbs; verbs < fStopVerbs; ++verbs) {
1917 switch (*verbs) {
1918 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001919 goto CONTOUR_END;
1920 case SkPath::kLine_Verb:
1921 ptCount += 1;
1922 break;
1923 case SkPath::kQuad_Verb:
1924 ptCount += 2;
1925 break;
1926 case SkPath::kCubic_Verb:
1927 ptCount += 3;
1928 break;
1929 default: // kClose_Verb, just keep going
1930 break;
1931 }
1932 }
1933CONTOUR_END:
1934 fCurrPtCount = ptCount;
1935 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001936 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001937}
1938
1939static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1940 return SkPoint::CrossProduct(p1 - p0, p2 - p0);
1941}
1942
1943static int find_max_y(const SkPoint pts[], int count) {
1944 SkASSERT(count > 0);
1945 SkScalar max = pts[0].fY;
1946 int maxIndex = 0;
1947 for (int i = 1; i < count; ++i) {
1948 if (pts[i].fY > max) {
1949 max = pts[i].fY;
1950 maxIndex = i;
1951 }
1952 }
1953 return maxIndex;
1954}
1955
reed@google.comcabaf1d2012-01-11 21:03:05 +00001956static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
1957 int i = index;
1958 for (;;) {
1959 i = (i + inc) % n;
1960 if (i == index) { // we wrapped around, so abort
1961 break;
1962 }
1963 if (pts[index] != pts[i]) { // found a different point, success!
1964 break;
1965 }
1966 }
1967 return i;
1968}
1969
reed@google.com69a99432012-01-10 18:00:10 +00001970bool SkPath::cheapComputeDirection(Direction* dir) const {
1971 // don't want to pay the cost for computing this if it
1972 // is unknown, so we don't call isConvex()
1973 const Convexity conv = this->getConvexityOrUnknown();
1974
1975 ContourIter iter(fVerbs, fPts);
1976
1977 for (; !iter.done(); iter.next()) {
1978 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00001979 if (n < 3) {
1980 continue;
1981 }
reed@google.com69a99432012-01-10 18:00:10 +00001982
reed@google.comcabaf1d2012-01-11 21:03:05 +00001983 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00001984 SkScalar cross = 0;
1985 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00001986 // we loop, skipping over degenerate or flat segments that will
1987 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00001988 for (int i = 0; i < n - 2; ++i) {
1989 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
1990 if (cross) {
1991 break;
1992 }
1993 }
1994 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00001995 int index = find_max_y(pts, n);
1996 // Find a next and prev index to use for the cross-product test,
1997 // but we try to find pts that form non-zero vectors from pts[index]
1998 //
1999 // Its possible that we can't find two non-degenerate vectors, so
2000 // we have to guard our search (e.g. all the pts could be in the
2001 // same place).
2002
2003 // we pass n - 1 instead of -1 so we don't foul up % operator by
2004 // passing it a negative LH argument.
2005 int prev = find_diff_pt(pts, index, n, n - 1);
2006 if (prev == index) {
2007 // completely degenerate, skip to next contour
2008 continue;
2009 }
2010 int next = find_diff_pt(pts, index, n, 1);
2011 SkASSERT(next != index);
2012 cross = cross_prod(pts[prev], pts[index], pts[next]);
reed@google.com188bfcf2012-01-17 18:26:38 +00002013 // if we get a zero, but the pts aren't on top of each other, then
2014 // we can just look at the direction
2015 if (0 == cross) {
2016 // construct the subtract so we get the correct Direction below
2017 cross = pts[index].fX - pts[next].fX;
2018 }
reed@google.com69a99432012-01-10 18:00:10 +00002019 }
2020 if (cross) {
2021 if (dir) {
2022 *dir = cross > 0 ? kCW_Direction : kCCW_Direction;
2023 }
2024 return true;
2025 }
2026 }
2027 return false; // unknown
2028}
2029