blob: 322bd1785da3f6e05ada597a50050568ab07a4f1 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000011#include "SkReader32.h"
12#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
14
15////////////////////////////////////////////////////////////////////////////
16
reed@google.com3563c9e2011-11-14 19:34:57 +000017/**
18 * Path.bounds is defined to be the bounds of all the control points.
19 * If we called bounds.join(r) we would skip r if r was empty, which breaks
20 * our promise. Hence we have a custom joiner that doesn't look at emptiness
21 */
22static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
23 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
24 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
25 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
26 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
27}
28
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000029static bool is_degenerate(const SkPath& path) {
30 SkPath::Iter iter(path, false);
31 SkPoint pts[4];
32 return SkPath::kDone_Verb == iter.next(pts);
33}
34
reed@android.com8a1c16f2008-12-17 15:59:43 +000035/* This guy's constructor/destructor bracket a path editing operation. It is
36 used when we know the bounds of the amount we are going to add to the path
37 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000038
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 It captures some state about the path up front (i.e. if it already has a
40 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000041 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000042
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043 It also notes if the path was originally degenerate, and if so, sets
44 isConvex to true. Thus it can only be used if the contour being added is
45 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000046 */
47class SkAutoPathBoundsUpdate {
48public:
49 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
50 this->init(path);
51 }
52
53 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
54 SkScalar right, SkScalar bottom) {
55 fRect.set(left, top, right, bottom);
56 this->init(path);
57 }
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000060 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000062 fPath->fBounds = fRect;
63 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000065 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000066 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 }
68 }
reed@google.comabf15c12011-01-18 20:35:51 +000069
reed@android.com8a1c16f2008-12-17 15:59:43 +000070private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000071 SkPath* fPath;
72 SkRect fRect;
73 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000074 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000075 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000078 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000080 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000081 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000083 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000084 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 }
86};
87
reed@android.comd252db02009-04-01 18:31:44 +000088static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
90 bounds->set(0, 0, 0, 0);
91 } else {
92 bounds->set(pts.begin(), pts.count());
93// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
94 }
95}
96
97////////////////////////////////////////////////////////////////////////////
98
99/*
100 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000101 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
103
104 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000105 1. If we encounter degenerate segments, remove them
106 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
107 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
108 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109*/
110
111////////////////////////////////////////////////////////////////////////////
112
reed@google.comd335d1d2012-01-12 18:17:11 +0000113// flag to require a moveTo if we begin with something else, like lineTo etc.
114#define INITIAL_LASTMOVETOINDEX_VALUE ~0
115
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000116SkPath::SkPath()
117 : fFillType(kWinding_FillType)
118 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000119 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000120 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000121 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
djsollen@google.com56c69772011-11-08 19:00:26 +0000122#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000123 fGenerationID = 0;
124#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000125}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126
127SkPath::SkPath(const SkPath& src) {
128 SkDEBUGCODE(src.validate();)
129 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000130#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000131 // the assignment operator above increments the ID so correct for that here
132 fGenerationID--;
133#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134}
135
136SkPath::~SkPath() {
137 SkDEBUGCODE(this->validate();)
138}
139
140SkPath& SkPath::operator=(const SkPath& src) {
141 SkDEBUGCODE(src.validate();)
142
143 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000144 fBounds = src.fBounds;
145 fPts = src.fPts;
146 fVerbs = src.fVerbs;
147 fFillType = src.fFillType;
148 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000149 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000150 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000151 fLastMoveToIndex = src.fLastMoveToIndex;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 }
154 SkDEBUGCODE(this->validate();)
155 return *this;
156}
157
reed@android.com3abec1d2009-03-02 05:36:20 +0000158bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000159 // note: don't need to look at isConvex or bounds, since just comparing the
160 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000161
162 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
163 // since it is only a cache of info in the fVerbs, but its a fast way to
164 // notice a difference
165
reed@android.com3abec1d2009-03-02 05:36:20 +0000166 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000167 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
168 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000169}
170
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171void SkPath::swap(SkPath& other) {
172 SkASSERT(&other != NULL);
173
174 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000175 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 fPts.swap(other.fPts);
177 fVerbs.swap(other.fVerbs);
178 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000179 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000180 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000181 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000182 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000183 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 }
185}
186
djsollen@google.com56c69772011-11-08 19:00:26 +0000187#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000188uint32_t SkPath::getGenerationID() const {
189 return fGenerationID;
190}
191#endif
192
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193void SkPath::reset() {
194 SkDEBUGCODE(this->validate();)
195
196 fPts.reset();
197 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000198 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000199 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000200 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000201 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000202 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203}
204
205void SkPath::rewind() {
206 SkDEBUGCODE(this->validate();)
207
208 fPts.rewind();
209 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000210 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000211 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000212 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000213 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000214 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215}
216
217bool SkPath::isEmpty() const {
218 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000219 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220}
221
caryclark@google.comf1316942011-07-26 19:54:45 +0000222/*
223 Determines if path is a rect by keeping track of changes in direction
224 and looking for a loop either clockwise or counterclockwise.
225
226 The direction is computed such that:
227 0: vertical up
228 1: horizontal right
229 2: vertical down
230 3: horizontal left
231
232A rectangle cycles up/right/down/left or up/left/down/right.
233
234The test fails if:
235 The path is closed, and followed by a line.
236 A second move creates a new endpoint.
237 A diagonal line is parsed.
238 There's more than four changes of direction.
239 There's a discontinuity on the line (e.g., a move in the middle)
240 The line reverses direction.
241 The rectangle doesn't complete a cycle.
242 The path contains a quadratic or cubic.
243 The path contains fewer than four points.
244 The final point isn't equal to the first point.
245
246It's OK if the path has:
247 Several colinear line segments composing a rectangle side.
248 Single points on the rectangle side.
249
250The direction takes advantage of the corners found since opposite sides
251must travel in opposite directions.
252
253FIXME: Allow colinear quads and cubics to be treated like lines.
254FIXME: If the API passes fill-only, return true if the filled stroke
255 is a rectangle, though the caller failed to close the path.
256 */
257bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000259
caryclark@google.comf1316942011-07-26 19:54:45 +0000260 int corners = 0;
261 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000262 first.set(0, 0);
263 last.set(0, 0);
264 int firstDirection = 0;
265 int lastDirection = 0;
266 int nextDirection = 0;
267 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000268 bool autoClose = false;
269 const uint8_t* verbs = fVerbs.begin();
270 const uint8_t* verbStop = fVerbs.end();
271 const SkPoint* pts = fPts.begin();
272 while (verbs != verbStop) {
273 switch (*verbs++) {
274 case kClose_Verb:
275 pts = fPts.begin();
276 autoClose = true;
277 case kLine_Verb: {
278 SkScalar left = last.fX;
279 SkScalar top = last.fY;
280 SkScalar right = pts->fX;
281 SkScalar bottom = pts->fY;
282 ++pts;
283 if (left != right && top != bottom) {
284 return false; // diagonal
285 }
286 if (left == right && top == bottom) {
287 break; // single point on side OK
288 }
289 nextDirection = (left != right) << 0 |
290 (left < right || top < bottom) << 1;
291 if (0 == corners) {
292 firstDirection = nextDirection;
293 first = last;
294 last = pts[-1];
295 corners = 1;
296 closedOrMoved = false;
297 break;
298 }
299 if (closedOrMoved) {
300 return false; // closed followed by a line
301 }
302 closedOrMoved = autoClose;
303 if (lastDirection != nextDirection) {
304 if (++corners > 4) {
305 return false; // too many direction changes
306 }
307 }
308 last = pts[-1];
309 if (lastDirection == nextDirection) {
310 break; // colinear segment
311 }
312 // Possible values for corners are 2, 3, and 4.
313 // When corners == 3, nextDirection opposes firstDirection.
314 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000315 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000316 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
317 if ((directionCycle ^ turn) != nextDirection) {
318 return false; // direction didn't follow cycle
319 }
320 break;
321 }
322 case kQuad_Verb:
323 case kCubic_Verb:
324 return false; // quadratic, cubic not allowed
325 case kMove_Verb:
326 last = *pts++;
327 closedOrMoved = true;
328 break;
329 }
330 lastDirection = nextDirection;
331 }
332 // Success if 4 corners and first point equals last
333 bool result = 4 == corners && first == last;
334 if (result && rect) {
335 *rect = getBounds();
336 }
337 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338}
339
340int SkPath::getPoints(SkPoint copy[], int max) const {
341 SkDEBUGCODE(this->validate();)
342
343 SkASSERT(max >= 0);
344 int count = fPts.count();
345 if (copy && max > 0 && count > 0) {
346 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
347 }
348 return count;
349}
350
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000351SkPoint SkPath::getPoint(int index) const {
352 if ((unsigned)index < (unsigned)fPts.count()) {
353 return fPts[index];
354 }
355 return SkPoint::Make(0, 0);
356}
357
reed@google.com294dd7b2011-10-11 11:58:32 +0000358bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 SkDEBUGCODE(this->validate();)
360
reed@google.com294dd7b2011-10-11 11:58:32 +0000361 int count = fPts.count();
362 if (count > 0) {
363 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 *lastPt = fPts[count - 1];
365 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000366 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000368 if (lastPt) {
369 lastPt->set(0, 0);
370 }
371 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
374void SkPath::setLastPt(SkScalar x, SkScalar y) {
375 SkDEBUGCODE(this->validate();)
376
377 int count = fPts.count();
378 if (count == 0) {
379 this->moveTo(x, y);
380 } else {
381 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000382 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 }
384}
385
reed@android.comd252db02009-04-01 18:31:44 +0000386void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000388 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389
reed@android.comd252db02009-04-01 18:31:44 +0000390 fBoundsIsDirty = false;
391 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392}
393
reed@google.com04863fa2011-05-15 04:08:24 +0000394void SkPath::setConvexity(Convexity c) {
395 if (fConvexity != c) {
396 fConvexity = c;
397 GEN_ID_INC;
398 }
399}
400
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401//////////////////////////////////////////////////////////////////////////////
402// Construction methods
403
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000404#define DIRTY_AFTER_EDIT \
405 do { \
406 fBoundsIsDirty = true; \
407 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000408 } while (0)
409
reed@android.com8a1c16f2008-12-17 15:59:43 +0000410void SkPath::incReserve(U16CPU inc) {
411 SkDEBUGCODE(this->validate();)
412
413 fVerbs.setReserve(fVerbs.count() + inc);
414 fPts.setReserve(fPts.count() + inc);
415
416 SkDEBUGCODE(this->validate();)
417}
418
419void SkPath::moveTo(SkScalar x, SkScalar y) {
420 SkDEBUGCODE(this->validate();)
421
422 int vc = fVerbs.count();
423 SkPoint* pt;
424
reed@google.comd335d1d2012-01-12 18:17:11 +0000425 // remember our index
426 fLastMoveToIndex = fPts.count();
427
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000428 pt = fPts.append();
429 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 pt->set(x, y);
431
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000432 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000433 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434}
435
436void SkPath::rMoveTo(SkScalar x, SkScalar y) {
437 SkPoint pt;
438 this->getLastPt(&pt);
439 this->moveTo(pt.fX + x, pt.fY + y);
440}
441
reed@google.comd335d1d2012-01-12 18:17:11 +0000442void SkPath::injectMoveToIfNeeded() {
443 if (fLastMoveToIndex < 0) {
444 SkScalar x, y;
445 if (fVerbs.count() == 0) {
446 x = y = 0;
447 } else {
448 const SkPoint& pt = fPts[~fLastMoveToIndex];
449 x = pt.fX;
450 y = pt.fY;
451 }
452 this->moveTo(x, y);
453 }
454}
455
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456void SkPath::lineTo(SkScalar x, SkScalar y) {
457 SkDEBUGCODE(this->validate();)
458
reed@google.comd335d1d2012-01-12 18:17:11 +0000459 this->injectMoveToIfNeeded();
460
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 fPts.append()->set(x, y);
462 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000463 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000465 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000466 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000467}
468
469void SkPath::rLineTo(SkScalar x, SkScalar y) {
470 SkPoint pt;
471 this->getLastPt(&pt);
472 this->lineTo(pt.fX + x, pt.fY + y);
473}
474
475void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
476 SkDEBUGCODE(this->validate();)
477
reed@google.comd335d1d2012-01-12 18:17:11 +0000478 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479
480 SkPoint* pts = fPts.append(2);
481 pts[0].set(x1, y1);
482 pts[1].set(x2, y2);
483 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000484 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000486 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000487 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488}
489
490void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
491 SkPoint pt;
492 this->getLastPt(&pt);
493 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
494}
495
496void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
497 SkScalar x3, SkScalar y3) {
498 SkDEBUGCODE(this->validate();)
499
reed@google.comd335d1d2012-01-12 18:17:11 +0000500 this->injectMoveToIfNeeded();
501
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502 SkPoint* pts = fPts.append(3);
503 pts[0].set(x1, y1);
504 pts[1].set(x2, y2);
505 pts[2].set(x3, y3);
506 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000507 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000508
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000509 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000510 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000511}
512
513void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
514 SkScalar x3, SkScalar y3) {
515 SkPoint pt;
516 this->getLastPt(&pt);
517 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
518 pt.fX + x3, pt.fY + y3);
519}
520
521void SkPath::close() {
522 SkDEBUGCODE(this->validate();)
523
524 int count = fVerbs.count();
525 if (count > 0) {
526 switch (fVerbs[count - 1]) {
527 case kLine_Verb:
528 case kQuad_Verb:
529 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000530 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000532 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533 break;
534 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000535 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536 break;
537 }
538 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000539
540 // signal that we need a moveTo to follow us (unless we're done)
541#if 0
542 if (fLastMoveToIndex >= 0) {
543 fLastMoveToIndex = ~fLastMoveToIndex;
544 }
545#else
546 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
547#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000548}
549
550///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000551
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552void SkPath::addRect(const SkRect& rect, Direction dir) {
553 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
554}
555
556void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
557 SkScalar bottom, Direction dir) {
558 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
559
560 this->incReserve(5);
561
562 this->moveTo(left, top);
563 if (dir == kCCW_Direction) {
564 this->lineTo(left, bottom);
565 this->lineTo(right, bottom);
566 this->lineTo(right, top);
567 } else {
568 this->lineTo(right, top);
569 this->lineTo(right, bottom);
570 this->lineTo(left, bottom);
571 }
572 this->close();
573}
574
575#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
576
577void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
578 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579 SkScalar w = rect.width();
580 SkScalar halfW = SkScalarHalf(w);
581 SkScalar h = rect.height();
582 SkScalar halfH = SkScalarHalf(h);
583
584 if (halfW <= 0 || halfH <= 0) {
585 return;
586 }
587
reed@google.comabf15c12011-01-18 20:35:51 +0000588 bool skip_hori = rx >= halfW;
589 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590
591 if (skip_hori && skip_vert) {
592 this->addOval(rect, dir);
593 return;
594 }
reed@google.comabf15c12011-01-18 20:35:51 +0000595
596 SkAutoPathBoundsUpdate apbu(this, rect);
597
reed@android.com8a1c16f2008-12-17 15:59:43 +0000598 if (skip_hori) {
599 rx = halfW;
600 } else if (skip_vert) {
601 ry = halfH;
602 }
603
604 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
605 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
606
607 this->incReserve(17);
608 this->moveTo(rect.fRight - rx, rect.fTop);
609 if (dir == kCCW_Direction) {
610 if (!skip_hori) {
611 this->lineTo(rect.fLeft + rx, rect.fTop); // top
612 }
613 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
614 rect.fLeft, rect.fTop + ry - sy,
615 rect.fLeft, rect.fTop + ry); // top-left
616 if (!skip_vert) {
617 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
618 }
619 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
620 rect.fLeft + rx - sx, rect.fBottom,
621 rect.fLeft + rx, rect.fBottom); // bot-left
622 if (!skip_hori) {
623 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
624 }
625 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
626 rect.fRight, rect.fBottom - ry + sy,
627 rect.fRight, rect.fBottom - ry); // bot-right
628 if (!skip_vert) {
629 this->lineTo(rect.fRight, rect.fTop + ry);
630 }
631 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
632 rect.fRight - rx + sx, rect.fTop,
633 rect.fRight - rx, rect.fTop); // top-right
634 } else {
635 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
636 rect.fRight, rect.fTop + ry - sy,
637 rect.fRight, rect.fTop + ry); // top-right
638 if (!skip_vert) {
639 this->lineTo(rect.fRight, rect.fBottom - ry);
640 }
641 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
642 rect.fRight - rx + sx, rect.fBottom,
643 rect.fRight - rx, rect.fBottom); // bot-right
644 if (!skip_hori) {
645 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
646 }
647 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
648 rect.fLeft, rect.fBottom - ry + sy,
649 rect.fLeft, rect.fBottom - ry); // bot-left
650 if (!skip_vert) {
651 this->lineTo(rect.fLeft, rect.fTop + ry); // left
652 }
653 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
654 rect.fLeft + rx - sx, rect.fTop,
655 rect.fLeft + rx, rect.fTop); // top-left
656 if (!skip_hori) {
657 this->lineTo(rect.fRight - rx, rect.fTop); // top
658 }
659 }
660 this->close();
661}
662
663static void add_corner_arc(SkPath* path, const SkRect& rect,
664 SkScalar rx, SkScalar ry, int startAngle,
665 SkPath::Direction dir, bool forceMoveTo) {
666 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
667 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000668
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 SkRect r;
670 r.set(-rx, -ry, rx, ry);
671
672 switch (startAngle) {
673 case 0:
674 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
675 break;
676 case 90:
677 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
678 break;
679 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
680 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000681 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 }
reed@google.comabf15c12011-01-18 20:35:51 +0000683
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 SkScalar start = SkIntToScalar(startAngle);
685 SkScalar sweep = SkIntToScalar(90);
686 if (SkPath::kCCW_Direction == dir) {
687 start += sweep;
688 sweep = -sweep;
689 }
reed@google.comabf15c12011-01-18 20:35:51 +0000690
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 path->arcTo(r, start, sweep, forceMoveTo);
692}
693
694void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
695 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000696 // abort before we invoke SkAutoPathBoundsUpdate()
697 if (rect.isEmpty()) {
698 return;
699 }
700
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701 SkAutoPathBoundsUpdate apbu(this, rect);
702
703 if (kCW_Direction == dir) {
704 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
705 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
706 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
707 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
708 } else {
709 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
710 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
711 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
712 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
713 }
714 this->close();
715}
716
717void SkPath::addOval(const SkRect& oval, Direction dir) {
718 SkAutoPathBoundsUpdate apbu(this, oval);
719
720 SkScalar cx = oval.centerX();
721 SkScalar cy = oval.centerY();
722 SkScalar rx = SkScalarHalf(oval.width());
723 SkScalar ry = SkScalarHalf(oval.height());
724#if 0 // these seem faster than using quads (1/2 the number of edges)
725 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
726 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
727
728 this->incReserve(13);
729 this->moveTo(cx + rx, cy);
730 if (dir == kCCW_Direction) {
731 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
732 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
733 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
734 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
735 } else {
736 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
737 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
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 }
741#else
742 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
743 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
744 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
745 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
746
747 /*
748 To handle imprecision in computing the center and radii, we revert to
749 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
750 to ensure that we don't exceed the oval's bounds *ever*, since we want
751 to use oval for our fast-bounds, rather than have to recompute it.
752 */
753 const SkScalar L = oval.fLeft; // cx - rx
754 const SkScalar T = oval.fTop; // cy - ry
755 const SkScalar R = oval.fRight; // cx + rx
756 const SkScalar B = oval.fBottom; // cy + ry
757
758 this->incReserve(17); // 8 quads + close
759 this->moveTo(R, cy);
760 if (dir == kCCW_Direction) {
761 this->quadTo( R, cy - sy, cx + mx, cy - my);
762 this->quadTo(cx + sx, T, cx , T);
763 this->quadTo(cx - sx, T, 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, B, cx , B);
767 this->quadTo(cx + sx, B, cx + mx, cy + my);
768 this->quadTo( R, cy + sy, R, cy );
769 } else {
770 this->quadTo( R, cy + sy, cx + mx, cy + my);
771 this->quadTo(cx + sx, B, cx , B);
772 this->quadTo(cx - sx, B, cx - mx, cy + my);
773 this->quadTo( L, cy + sy, L, cy );
774 this->quadTo( L, cy - sy, cx - mx, cy - my);
775 this->quadTo(cx - sx, T, cx , T);
776 this->quadTo(cx + sx, T, cx + mx, cy - my);
777 this->quadTo( R, cy - sy, R, cy );
778 }
779#endif
780 this->close();
781}
782
783void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
784 if (r > 0) {
785 SkRect rect;
786 rect.set(x - r, y - r, x + r, y + r);
787 this->addOval(rect, dir);
788 }
789}
790
791#include "SkGeometry.h"
792
793static int build_arc_points(const SkRect& oval, SkScalar startAngle,
794 SkScalar sweepAngle,
795 SkPoint pts[kSkBuildQuadArcStorage]) {
796 SkVector start, stop;
797
798 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
799 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
800 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000801
802 /* If the sweep angle is nearly (but less than) 360, then due to precision
803 loss in radians-conversion and/or sin/cos, we may end up with coincident
804 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
805 of drawing a nearly complete circle (good).
806 e.g. canvas.drawArc(0, 359.99, ...)
807 -vs- canvas.drawArc(0, 359.9, ...)
808 We try to detect this edge case, and tweak the stop vector
809 */
810 if (start == stop) {
811 SkScalar sw = SkScalarAbs(sweepAngle);
812 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
813 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
814 // make a guess at a tiny angle (in radians) to tweak by
815 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
816 // not sure how much will be enough, so we use a loop
817 do {
818 stopRad -= deltaRad;
819 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
820 } while (start == stop);
821 }
822 }
823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000825
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
827 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000828
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 return SkBuildQuadArc(start, stop,
830 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
831 &matrix, pts);
832}
833
834void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
835 bool forceMoveTo) {
836 if (oval.width() < 0 || oval.height() < 0) {
837 return;
838 }
839
840 SkPoint pts[kSkBuildQuadArcStorage];
841 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
842 SkASSERT((count & 1) == 1);
843
844 if (fVerbs.count() == 0) {
845 forceMoveTo = true;
846 }
847 this->incReserve(count);
848 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
849 for (int i = 1; i < count; i += 2) {
850 this->quadTo(pts[i], pts[i+1]);
851 }
852}
853
854void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
855 SkScalar sweepAngle) {
856 if (oval.isEmpty() || 0 == sweepAngle) {
857 return;
858 }
859
860 const SkScalar kFullCircleAngle = SkIntToScalar(360);
861
862 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
863 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
864 return;
865 }
866
867 SkPoint pts[kSkBuildQuadArcStorage];
868 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
869
870 this->incReserve(count);
871 this->moveTo(pts[0]);
872 for (int i = 1; i < count; i += 2) {
873 this->quadTo(pts[i], pts[i+1]);
874 }
875}
876
877/*
878 Need to handle the case when the angle is sharp, and our computed end-points
879 for the arc go behind pt1 and/or p2...
880*/
881void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
882 SkScalar radius) {
883 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000884
reed@android.com8a1c16f2008-12-17 15:59:43 +0000885 // need to know our prev pt so we can construct tangent vectors
886 {
887 SkPoint start;
888 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000889 // Handle degenerate cases by adding a line to the first point and
890 // bailing out.
891 if ((x1 == start.fX && y1 == start.fY) ||
892 (x1 == x2 && y1 == y2) ||
893 radius == 0) {
894 this->lineTo(x1, y1);
895 return;
896 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 before.setNormalize(x1 - start.fX, y1 - start.fY);
898 after.setNormalize(x2 - x1, y2 - y1);
899 }
reed@google.comabf15c12011-01-18 20:35:51 +0000900
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 SkScalar cosh = SkPoint::DotProduct(before, after);
902 SkScalar sinh = SkPoint::CrossProduct(before, after);
903
904 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000905 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 return;
907 }
reed@google.comabf15c12011-01-18 20:35:51 +0000908
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
910 if (dist < 0) {
911 dist = -dist;
912 }
913
914 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
915 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
916 SkRotationDirection arcDir;
917
918 // now turn before/after into normals
919 if (sinh > 0) {
920 before.rotateCCW();
921 after.rotateCCW();
922 arcDir = kCW_SkRotationDirection;
923 } else {
924 before.rotateCW();
925 after.rotateCW();
926 arcDir = kCCW_SkRotationDirection;
927 }
928
929 SkMatrix matrix;
930 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000931
reed@android.com8a1c16f2008-12-17 15:59:43 +0000932 matrix.setScale(radius, radius);
933 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
934 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000935
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000937
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 this->incReserve(count);
939 // [xx,yy] == pts[0]
940 this->lineTo(xx, yy);
941 for (int i = 1; i < count; i += 2) {
942 this->quadTo(pts[i], pts[i+1]);
943 }
944}
945
946///////////////////////////////////////////////////////////////////////////////
947
948void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
949 SkMatrix matrix;
950
951 matrix.setTranslate(dx, dy);
952 this->addPath(path, matrix);
953}
954
955void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
956 this->incReserve(path.fPts.count());
957
schenney@chromium.org6630d8d2012-01-04 21:05:51 +0000958 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000959 SkPoint pts[4];
960 Verb verb;
961
962 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
963
964 while ((verb = iter.next(pts)) != kDone_Verb) {
965 switch (verb) {
966 case kMove_Verb:
967 proc(matrix, &pts[0], &pts[0], 1);
968 this->moveTo(pts[0]);
969 break;
970 case kLine_Verb:
971 proc(matrix, &pts[1], &pts[1], 1);
972 this->lineTo(pts[1]);
973 break;
974 case kQuad_Verb:
975 proc(matrix, &pts[1], &pts[1], 2);
976 this->quadTo(pts[1], pts[2]);
977 break;
978 case kCubic_Verb:
979 proc(matrix, &pts[1], &pts[1], 3);
980 this->cubicTo(pts[1], pts[2], pts[3]);
981 break;
982 case kClose_Verb:
983 this->close();
984 break;
985 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000986 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987 }
988 }
989}
990
991///////////////////////////////////////////////////////////////////////////////
992
993static const uint8_t gPtsInVerb[] = {
994 1, // kMove
995 1, // kLine
996 2, // kQuad
997 3, // kCubic
998 0, // kClose
999 0 // kDone
1000};
1001
1002// ignore the initial moveto, and stop when the 1st contour ends
1003void SkPath::pathTo(const SkPath& path) {
1004 int i, vcount = path.fVerbs.count();
1005 if (vcount == 0) {
1006 return;
1007 }
1008
1009 this->incReserve(vcount);
1010
1011 const uint8_t* verbs = path.fVerbs.begin();
1012 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1013
1014 SkASSERT(verbs[0] == kMove_Verb);
1015 for (i = 1; i < vcount; i++) {
1016 switch (verbs[i]) {
1017 case kLine_Verb:
1018 this->lineTo(pts[0].fX, pts[0].fY);
1019 break;
1020 case kQuad_Verb:
1021 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1022 break;
1023 case kCubic_Verb:
1024 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1025 pts[2].fX, pts[2].fY);
1026 break;
1027 case kClose_Verb:
1028 return;
1029 }
1030 pts += gPtsInVerb[verbs[i]];
1031 }
1032}
1033
1034// ignore the last point of the 1st contour
1035void SkPath::reversePathTo(const SkPath& path) {
1036 int i, vcount = path.fVerbs.count();
1037 if (vcount == 0) {
1038 return;
1039 }
1040
1041 this->incReserve(vcount);
1042
1043 const uint8_t* verbs = path.fVerbs.begin();
1044 const SkPoint* pts = path.fPts.begin();
1045
1046 SkASSERT(verbs[0] == kMove_Verb);
1047 for (i = 1; i < vcount; i++) {
1048 int n = gPtsInVerb[verbs[i]];
1049 if (n == 0) {
1050 break;
1051 }
1052 pts += n;
1053 }
1054
1055 while (--i > 0) {
1056 switch (verbs[i]) {
1057 case kLine_Verb:
1058 this->lineTo(pts[-1].fX, pts[-1].fY);
1059 break;
1060 case kQuad_Verb:
1061 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1062 break;
1063 case kCubic_Verb:
1064 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1065 pts[-3].fX, pts[-3].fY);
1066 break;
1067 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001068 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 break;
1070 }
1071 pts -= gPtsInVerb[verbs[i]];
1072 }
1073}
1074
reed@google.com63d73742012-01-10 15:33:12 +00001075void SkPath::reverseAddPath(const SkPath& src) {
1076 this->incReserve(src.fPts.count());
1077
1078 const SkPoint* startPts = src.fPts.begin();
1079 const SkPoint* pts = src.fPts.end();
1080 const uint8_t* startVerbs = src.fVerbs.begin();
1081 const uint8_t* verbs = src.fVerbs.end();
1082
1083 bool needMove = true;
1084 bool needClose = false;
1085 while (verbs > startVerbs) {
1086 uint8_t v = *--verbs;
1087 int n = gPtsInVerb[v];
1088
1089 if (needMove) {
1090 --pts;
1091 this->moveTo(pts->fX, pts->fY);
1092 needMove = false;
1093 }
1094 pts -= n;
1095 switch (v) {
1096 case kMove_Verb:
1097 if (needClose) {
1098 this->close();
1099 needClose = false;
1100 }
1101 needMove = true;
1102 pts += 1; // so we see the point in "if (needMove)" above
1103 break;
1104 case kLine_Verb:
1105 this->lineTo(pts[0]);
1106 break;
1107 case kQuad_Verb:
1108 this->quadTo(pts[1], pts[0]);
1109 break;
1110 case kCubic_Verb:
1111 this->cubicTo(pts[2], pts[1], pts[0]);
1112 break;
1113 case kClose_Verb:
1114 needClose = true;
1115 break;
1116 default:
1117 SkASSERT(!"unexpected verb");
1118 }
1119 }
1120}
1121
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122///////////////////////////////////////////////////////////////////////////////
1123
1124void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1125 SkMatrix matrix;
1126
1127 matrix.setTranslate(dx, dy);
1128 this->transform(matrix, dst);
1129}
1130
1131#include "SkGeometry.h"
1132
1133static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1134 int level = 2) {
1135 if (--level >= 0) {
1136 SkPoint tmp[5];
1137
1138 SkChopQuadAtHalf(pts, tmp);
1139 subdivide_quad_to(path, &tmp[0], level);
1140 subdivide_quad_to(path, &tmp[2], level);
1141 } else {
1142 path->quadTo(pts[1], pts[2]);
1143 }
1144}
1145
1146static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1147 int level = 2) {
1148 if (--level >= 0) {
1149 SkPoint tmp[7];
1150
1151 SkChopCubicAtHalf(pts, tmp);
1152 subdivide_cubic_to(path, &tmp[0], level);
1153 subdivide_cubic_to(path, &tmp[3], level);
1154 } else {
1155 path->cubicTo(pts[1], pts[2], pts[3]);
1156 }
1157}
1158
1159void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1160 SkDEBUGCODE(this->validate();)
1161 if (dst == NULL) {
1162 dst = (SkPath*)this;
1163 }
1164
tomhudson@google.com8d430182011-06-06 19:11:19 +00001165 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166 SkPath tmp;
1167 tmp.fFillType = fFillType;
1168
1169 SkPath::Iter iter(*this, false);
1170 SkPoint pts[4];
1171 SkPath::Verb verb;
1172
1173 while ((verb = iter.next(pts)) != kDone_Verb) {
1174 switch (verb) {
1175 case kMove_Verb:
1176 tmp.moveTo(pts[0]);
1177 break;
1178 case kLine_Verb:
1179 tmp.lineTo(pts[1]);
1180 break;
1181 case kQuad_Verb:
1182 subdivide_quad_to(&tmp, pts);
1183 break;
1184 case kCubic_Verb:
1185 subdivide_cubic_to(&tmp, pts);
1186 break;
1187 case kClose_Verb:
1188 tmp.close();
1189 break;
1190 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001191 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 break;
1193 }
1194 }
1195
1196 dst->swap(tmp);
1197 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1198 } else {
1199 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001200 // fBoundsIsDirty before we set it
1201 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001203 matrix.mapRect(&dst->fBounds, fBounds);
1204 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001206 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001207 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208 }
1209
1210 if (this != dst) {
1211 dst->fVerbs = fVerbs;
1212 dst->fPts.setCount(fPts.count());
1213 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001214 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001215 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216 }
1217 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1218 SkDEBUGCODE(dst->validate();)
1219 }
1220}
1221
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222///////////////////////////////////////////////////////////////////////////////
1223///////////////////////////////////////////////////////////////////////////////
1224
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001225enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001226 kEmptyContour_SegmentState, // The current contour is empty. We may be
1227 // starting processing or we may have just
1228 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001229 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1230 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1231 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232};
1233
1234SkPath::Iter::Iter() {
1235#ifdef SK_DEBUG
1236 fPts = NULL;
1237 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001238 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001239 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240#endif
1241 // need to init enough to make next() harmlessly return kDone_Verb
1242 fVerbs = NULL;
1243 fVerbStop = NULL;
1244 fNeedClose = false;
1245}
1246
1247SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1248 this->setPath(path, forceClose);
1249}
1250
1251void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1252 fPts = path.fPts.begin();
1253 fVerbs = path.fVerbs.begin();
1254 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001255 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001256 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 fForceClose = SkToU8(forceClose);
1258 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001259 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260}
1261
1262bool SkPath::Iter::isClosedContour() const {
1263 if (fVerbs == NULL || fVerbs == fVerbStop) {
1264 return false;
1265 }
1266 if (fForceClose) {
1267 return true;
1268 }
1269
1270 const uint8_t* verbs = fVerbs;
1271 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 if (kMove_Verb == *verbs) {
1274 verbs += 1; // skip the initial moveto
1275 }
1276
1277 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001278 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 if (kMove_Verb == v) {
1280 break;
1281 }
1282 if (kClose_Verb == v) {
1283 return true;
1284 }
1285 }
1286 return false;
1287}
1288
1289SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1290 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001291 // A special case: if both points are NaN, SkPoint::operation== returns
1292 // false, but the iterator expects that they are treated as the same.
1293 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001294 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1295 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001296 return kClose_Verb;
1297 }
1298
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 if (pts) {
1300 pts[0] = fLastPt;
1301 pts[1] = fMoveTo;
1302 }
1303 fLastPt = fMoveTo;
1304 fCloseLine = true;
1305 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001306 } else {
1307 pts[0] = fMoveTo;
1308 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310}
1311
1312bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001313 if (fSegmentState == kAfterMove_SegmentState) {
1314 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 if (pts) {
1316 *pts = fMoveTo;
1317 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001318 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001320 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1321 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 if (pts) {
1323 *pts = fPts[-1];
1324 }
1325 }
1326 return false;
1327}
1328
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001329void SkPath::Iter::consumeDegenerateSegments() {
1330 // We need to step over anything that will not move the current draw point
1331 // forward before the next move is seen
1332 const uint8_t* lastMoveVerb = 0;
1333 const SkPoint* lastMovePt = 0;
1334 SkPoint lastPt = fLastPt;
1335 while (fVerbs != fVerbStop) {
1336 unsigned verb = *fVerbs;
1337 switch (verb) {
1338 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001339 // Keep a record of this most recent move
1340 lastMoveVerb = fVerbs;
1341 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001342 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001343 fVerbs++;
1344 fPts++;
1345 break;
1346
1347 case kClose_Verb:
1348 // A close when we are in a segment is always valid
1349 if (fSegmentState == kAfterPrimitive_SegmentState) {
1350 return;
1351 }
1352 // A close at any other time must be ignored
1353 fVerbs++;
1354 break;
1355
1356 case kLine_Verb:
1357 if (!IsLineDegenerate(lastPt, fPts[0])) {
1358 if (lastMoveVerb) {
1359 fVerbs = lastMoveVerb;
1360 fPts = lastMovePt;
1361 return;
1362 }
1363 return;
1364 }
1365 // Ignore this line and continue
1366 fVerbs++;
1367 fPts++;
1368 break;
1369
1370 case kQuad_Verb:
1371 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1372 if (lastMoveVerb) {
1373 fVerbs = lastMoveVerb;
1374 fPts = lastMovePt;
1375 return;
1376 }
1377 return;
1378 }
1379 // Ignore this line and continue
1380 fVerbs++;
1381 fPts += 2;
1382 break;
1383
1384 case kCubic_Verb:
1385 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1386 if (lastMoveVerb) {
1387 fVerbs = lastMoveVerb;
1388 fPts = lastMovePt;
1389 return;
1390 }
1391 return;
1392 }
1393 // Ignore this line and continue
1394 fVerbs++;
1395 fPts += 3;
1396 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001397
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001398 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001399 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001400 }
1401 }
1402}
1403
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001405 this->consumeDegenerateSegments();
1406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001408 // Close the curve if requested and if there is some curve to close
1409 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 if (kLine_Verb == this->autoClose(pts)) {
1411 return kLine_Verb;
1412 }
1413 fNeedClose = false;
1414 return kClose_Verb;
1415 }
1416 return kDone_Verb;
1417 }
1418
1419 unsigned verb = *fVerbs++;
1420 const SkPoint* srcPts = fPts;
1421
1422 switch (verb) {
1423 case kMove_Verb:
1424 if (fNeedClose) {
1425 fVerbs -= 1;
1426 verb = this->autoClose(pts);
1427 if (verb == kClose_Verb) {
1428 fNeedClose = false;
1429 }
1430 return (Verb)verb;
1431 }
1432 if (fVerbs == fVerbStop) { // might be a trailing moveto
1433 return kDone_Verb;
1434 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001435 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 if (pts) {
1437 pts[0] = *srcPts;
1438 }
1439 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001440 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001441 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 fNeedClose = fForceClose;
1443 break;
1444 case kLine_Verb:
1445 if (this->cons_moveTo(pts)) {
1446 return kMove_Verb;
1447 }
1448 if (pts) {
1449 pts[1] = srcPts[0];
1450 }
1451 fLastPt = srcPts[0];
1452 fCloseLine = false;
1453 srcPts += 1;
1454 break;
1455 case kQuad_Verb:
1456 if (this->cons_moveTo(pts)) {
1457 return kMove_Verb;
1458 }
1459 if (pts) {
1460 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1461 }
1462 fLastPt = srcPts[1];
1463 srcPts += 2;
1464 break;
1465 case kCubic_Verb:
1466 if (this->cons_moveTo(pts)) {
1467 return kMove_Verb;
1468 }
1469 if (pts) {
1470 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1471 }
1472 fLastPt = srcPts[2];
1473 srcPts += 3;
1474 break;
1475 case kClose_Verb:
1476 verb = this->autoClose(pts);
1477 if (verb == kLine_Verb) {
1478 fVerbs -= 1;
1479 } else {
1480 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001481 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001483 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 break;
1485 }
1486 fPts = srcPts;
1487 return (Verb)verb;
1488}
1489
1490///////////////////////////////////////////////////////////////////////////////
1491
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001492SkPath::RawIter::RawIter() {
1493#ifdef SK_DEBUG
1494 fPts = NULL;
1495 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1496#endif
1497 // need to init enough to make next() harmlessly return kDone_Verb
1498 fVerbs = NULL;
1499 fVerbStop = NULL;
1500}
1501
1502SkPath::RawIter::RawIter(const SkPath& path) {
1503 this->setPath(path);
1504}
1505
1506void SkPath::RawIter::setPath(const SkPath& path) {
1507 fPts = path.fPts.begin();
1508 fVerbs = path.fVerbs.begin();
1509 fVerbStop = path.fVerbs.end();
1510 fMoveTo.fX = fMoveTo.fY = 0;
1511 fLastPt.fX = fLastPt.fY = 0;
1512}
1513
1514SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1515 if (fVerbs == fVerbStop) {
1516 return kDone_Verb;
1517 }
1518
1519 unsigned verb = *fVerbs++;
1520 const SkPoint* srcPts = fPts;
1521
1522 switch (verb) {
1523 case kMove_Verb:
1524 if (pts) {
1525 pts[0] = *srcPts;
1526 }
1527 fMoveTo = srcPts[0];
1528 fLastPt = fMoveTo;
1529 srcPts += 1;
1530 break;
1531 case kLine_Verb:
1532 if (pts) {
1533 pts[0] = fLastPt;
1534 pts[1] = srcPts[0];
1535 }
1536 fLastPt = srcPts[0];
1537 srcPts += 1;
1538 break;
1539 case kQuad_Verb:
1540 if (pts) {
1541 pts[0] = fLastPt;
1542 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1543 }
1544 fLastPt = srcPts[1];
1545 srcPts += 2;
1546 break;
1547 case kCubic_Verb:
1548 if (pts) {
1549 pts[0] = fLastPt;
1550 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1551 }
1552 fLastPt = srcPts[2];
1553 srcPts += 3;
1554 break;
1555 case kClose_Verb:
1556 fLastPt = fMoveTo;
1557 if (pts) {
1558 pts[0] = fMoveTo;
1559 }
1560 break;
1561 }
1562 fPts = srcPts;
1563 return (Verb)verb;
1564}
1565
1566///////////////////////////////////////////////////////////////////////////////
1567
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568/*
1569 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1570*/
1571
reed@google.com73945652011-04-25 19:04:27 +00001572void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 SkDEBUGCODE(this->validate();)
1574
1575 buffer.write32(fPts.count());
1576 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001577 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1579 buffer.writePad(fVerbs.begin(), fVerbs.count());
1580}
1581
reed@google.com73945652011-04-25 19:04:27 +00001582void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 fPts.setCount(buffer.readS32());
1584 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001585 uint32_t packed = buffer.readS32();
1586 fFillType = packed >> 8;
1587 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1589 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001590
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001591 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001592 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593
1594 SkDEBUGCODE(this->validate();)
1595}
1596
1597///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599void SkPath::dump(bool forceClose, const char title[]) const {
1600 Iter iter(*this, forceClose);
1601 SkPoint pts[4];
1602 Verb verb;
1603
1604 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1605 title ? title : "");
1606
1607 while ((verb = iter.next(pts)) != kDone_Verb) {
1608 switch (verb) {
1609 case kMove_Verb:
1610#ifdef SK_CAN_USE_FLOAT
1611 SkDebugf(" path: moveTo [%g %g]\n",
1612 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1613#else
1614 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1615#endif
1616 break;
1617 case kLine_Verb:
1618#ifdef SK_CAN_USE_FLOAT
1619 SkDebugf(" path: lineTo [%g %g]\n",
1620 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1621#else
1622 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1623#endif
1624 break;
1625 case kQuad_Verb:
1626#ifdef SK_CAN_USE_FLOAT
1627 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1628 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1629 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1630#else
1631 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1632 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1633#endif
1634 break;
1635 case kCubic_Verb:
1636#ifdef SK_CAN_USE_FLOAT
1637 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1638 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1639 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1640 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1641#else
1642 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1643 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1644 pts[3].fX, pts[3].fY);
1645#endif
1646 break;
1647 case kClose_Verb:
1648 SkDebugf(" path: close\n");
1649 break;
1650 default:
1651 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1652 verb = kDone_Verb; // stop the loop
1653 break;
1654 }
1655 }
1656 SkDebugf("path: done %s\n", title ? title : "");
1657}
1658
reed@android.come522ca52009-11-23 20:10:41 +00001659void SkPath::dump() const {
1660 this->dump(false);
1661}
1662
1663#ifdef SK_DEBUG
1664void SkPath::validate() const {
1665 SkASSERT(this != NULL);
1666 SkASSERT((fFillType & ~3) == 0);
1667 fPts.validate();
1668 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001669
reed@android.come522ca52009-11-23 20:10:41 +00001670 if (!fBoundsIsDirty) {
1671 SkRect bounds;
1672 compute_pt_bounds(&bounds, fPts);
1673 if (fPts.count() <= 1) {
1674 // if we're empty, fBounds may be empty but translated, so we can't
1675 // necessarily compare to bounds directly
1676 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1677 // be [2, 2, 2, 2]
1678 SkASSERT(bounds.isEmpty());
1679 SkASSERT(fBounds.isEmpty());
1680 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001681 if (bounds.isEmpty()) {
1682 SkASSERT(fBounds.isEmpty());
1683 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001684 if (!fBounds.isEmpty()) {
1685 SkASSERT(fBounds.contains(bounds));
1686 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001687 }
reed@android.come522ca52009-11-23 20:10:41 +00001688 }
1689 }
reed@google.com10296cc2011-09-21 12:29:05 +00001690
1691 uint32_t mask = 0;
1692 for (int i = 0; i < fVerbs.count(); i++) {
1693 switch (fVerbs[i]) {
1694 case kLine_Verb:
1695 mask |= kLine_SegmentMask;
1696 break;
1697 case kQuad_Verb:
1698 mask |= kQuad_SegmentMask;
1699 break;
1700 case kCubic_Verb:
1701 mask |= kCubic_SegmentMask;
1702 }
1703 }
1704 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001705}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706#endif
reed@android.come522ca52009-11-23 20:10:41 +00001707
reed@google.com04863fa2011-05-15 04:08:24 +00001708///////////////////////////////////////////////////////////////////////////////
1709
reed@google.com0b7b9822011-05-16 12:29:27 +00001710static int sign(SkScalar x) { return x < 0; }
1711#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001712
reed@google.com04863fa2011-05-15 04:08:24 +00001713static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001714 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001715}
1716
1717// only valid for a single contour
1718struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001719 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001720 fSign = 0;
1721 // warnings
1722 fCurrPt.set(0, 0);
1723 fVec0.set(0, 0);
1724 fVec1.set(0, 0);
1725 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001726
1727 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001728 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001729 }
1730
1731 SkPath::Convexity getConvexity() const { return fConvexity; }
1732
1733 void addPt(const SkPoint& pt) {
1734 if (SkPath::kConcave_Convexity == fConvexity) {
1735 return;
1736 }
1737
1738 if (0 == fPtCount) {
1739 fCurrPt = pt;
1740 ++fPtCount;
1741 } else {
1742 SkVector vec = pt - fCurrPt;
1743 if (vec.fX || vec.fY) {
1744 fCurrPt = pt;
1745 if (++fPtCount == 2) {
1746 fFirstVec = fVec1 = vec;
1747 } else {
1748 SkASSERT(fPtCount > 2);
1749 this->addVec(vec);
1750 }
reed@google.com85b6e392011-05-15 20:25:17 +00001751
1752 int sx = sign(vec.fX);
1753 int sy = sign(vec.fY);
1754 fDx += (sx != fSx);
1755 fDy += (sy != fSy);
1756 fSx = sx;
1757 fSy = sy;
1758
1759 if (fDx > 3 || fDy > 3) {
1760 fConvexity = SkPath::kConcave_Convexity;
1761 }
reed@google.com04863fa2011-05-15 04:08:24 +00001762 }
1763 }
1764 }
1765
1766 void close() {
1767 if (fPtCount > 2) {
1768 this->addVec(fFirstVec);
1769 }
1770 }
1771
1772private:
1773 void addVec(const SkVector& vec) {
1774 SkASSERT(vec.fX || vec.fY);
1775 fVec0 = fVec1;
1776 fVec1 = vec;
1777 int sign = CrossProductSign(fVec0, fVec1);
1778 if (0 == fSign) {
1779 fSign = sign;
1780 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001781 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001782 fConvexity = SkPath::kConcave_Convexity;
1783 }
1784 }
1785 }
1786
1787 SkPoint fCurrPt;
1788 SkVector fVec0, fVec1, fFirstVec;
1789 int fPtCount; // non-degenerate points
1790 int fSign;
1791 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001792 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001793};
1794
1795SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1796 SkPoint pts[4];
1797 SkPath::Verb verb;
1798 SkPath::Iter iter(path, true);
1799
1800 int contourCount = 0;
1801 int count;
1802 Convexicator state;
1803
1804 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1805 switch (verb) {
1806 case kMove_Verb:
1807 if (++contourCount > 1) {
1808 return kConcave_Convexity;
1809 }
1810 pts[1] = pts[0];
1811 count = 1;
1812 break;
1813 case kLine_Verb: count = 1; break;
1814 case kQuad_Verb: count = 2; break;
1815 case kCubic_Verb: count = 3; break;
1816 case kClose_Verb:
1817 state.close();
1818 count = 0;
1819 break;
1820 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001821 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001822 return kConcave_Convexity;
1823 }
1824
1825 for (int i = 1; i <= count; i++) {
1826 state.addPt(pts[i]);
1827 }
1828 // early exit
1829 if (kConcave_Convexity == state.getConvexity()) {
1830 return kConcave_Convexity;
1831 }
1832 }
1833 return state.getConvexity();
1834}
reed@google.com69a99432012-01-10 18:00:10 +00001835
1836///////////////////////////////////////////////////////////////////////////////
1837
1838class ContourIter {
1839public:
1840 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1841
1842 bool done() const { return fDone; }
1843 // if !done() then these may be called
1844 int count() const { return fCurrPtCount; }
1845 const SkPoint* pts() const { return fCurrPt; }
1846 void next();
1847
1848private:
1849 int fCurrPtCount;
1850 const SkPoint* fCurrPt;
1851 const uint8_t* fCurrVerb;
1852 const uint8_t* fStopVerbs;
1853 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00001854 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001855};
1856
1857ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1858 const SkTDArray<SkPoint>& pts) {
1859 fStopVerbs = verbs.begin() + verbs.count();
1860
1861 fDone = false;
1862 fCurrPt = pts.begin();
1863 fCurrVerb = verbs.begin();
1864 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00001865 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00001866 this->next();
1867}
1868
1869void ContourIter::next() {
1870 if (fCurrVerb >= fStopVerbs) {
1871 fDone = true;
1872 }
1873 if (fDone) {
1874 return;
1875 }
1876
1877 // skip pts of prev contour
1878 fCurrPt += fCurrPtCount;
1879
1880 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1881 int ptCount = 1; // moveTo
1882 const uint8_t* verbs = fCurrVerb;
1883
1884 for (++verbs; verbs < fStopVerbs; ++verbs) {
1885 switch (*verbs) {
1886 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00001887 goto CONTOUR_END;
1888 case SkPath::kLine_Verb:
1889 ptCount += 1;
1890 break;
1891 case SkPath::kQuad_Verb:
1892 ptCount += 2;
1893 break;
1894 case SkPath::kCubic_Verb:
1895 ptCount += 3;
1896 break;
1897 default: // kClose_Verb, just keep going
1898 break;
1899 }
1900 }
1901CONTOUR_END:
1902 fCurrPtCount = ptCount;
1903 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00001904 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00001905}
1906
1907static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1908 return SkPoint::CrossProduct(p1 - p0, p2 - p0);
1909}
1910
reed@google.comc1ea60a2012-01-31 15:15:36 +00001911// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00001912static int find_max_y(const SkPoint pts[], int count) {
1913 SkASSERT(count > 0);
1914 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00001915 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00001916 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00001917 SkScalar y = pts[i].fY;
1918 if (y > max) {
1919 max = y;
1920 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00001921 }
1922 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00001923 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00001924}
1925
reed@google.comcabaf1d2012-01-11 21:03:05 +00001926static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
1927 int i = index;
1928 for (;;) {
1929 i = (i + inc) % n;
1930 if (i == index) { // we wrapped around, so abort
1931 break;
1932 }
1933 if (pts[index] != pts[i]) { // found a different point, success!
1934 break;
1935 }
1936 }
1937 return i;
1938}
1939
reed@google.comc1ea60a2012-01-31 15:15:36 +00001940/**
1941 * Starting at index, and moving forward (incrementing), find the xmin and
1942 * xmax of the contiguous points that have the same Y.
1943 */
1944static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
1945 int* maxIndexPtr) {
1946 const SkScalar y = pts[index].fY;
1947 SkScalar min = pts[index].fX;
1948 SkScalar max = min;
1949 int minIndex = index;
1950 int maxIndex = index;
1951 for (int i = index + 1; i < n; ++i) {
1952 if (pts[i].fY != y) {
1953 break;
1954 }
1955 SkScalar x = pts[i].fX;
1956 if (x < min) {
1957 min = x;
1958 minIndex = i;
1959 } else if (x > max) {
1960 max = x;
1961 maxIndex = i;
1962 }
1963 }
1964 *maxIndexPtr = maxIndex;
1965 return minIndex;
1966}
1967
reed@google.comac8543f2012-01-30 20:51:25 +00001968static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
1969 if (dir) {
1970 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
1971 }
1972 return true;
1973}
1974
reed@google.comc1ea60a2012-01-31 15:15:36 +00001975#if 0
1976#include "SkString.h"
1977#include "../utils/SkParsePath.h"
1978static void dumpPath(const SkPath& path) {
1979 SkString str;
1980 SkParsePath::ToSVGString(path, &str);
1981 SkDebugf("%s\n", str.c_str());
1982}
1983#endif
1984
reed@google.comac8543f2012-01-30 20:51:25 +00001985/*
1986 * We loop through all contours, and keep the computed cross-product of the
1987 * contour that contained the global y-max. If we just look at the first
1988 * contour, we may find one that is wound the opposite way (correctly) since
1989 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
1990 * that is outer most (or at least has the global y-max) before we can consider
1991 * its cross product.
1992 */
reed@google.com69a99432012-01-10 18:00:10 +00001993bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00001994// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00001995 // don't want to pay the cost for computing this if it
1996 // is unknown, so we don't call isConvex()
1997 const Convexity conv = this->getConvexityOrUnknown();
1998
1999 ContourIter iter(fVerbs, fPts);
2000
reed@google.comac8543f2012-01-30 20:51:25 +00002001 // initialize with our logical y-min
2002 SkScalar ymax = this->getBounds().fTop;
2003 SkScalar ymaxCross = 0;
2004
reed@google.com69a99432012-01-10 18:00:10 +00002005 for (; !iter.done(); iter.next()) {
2006 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002007 if (n < 3) {
2008 continue;
2009 }
reed@google.comac8543f2012-01-30 20:51:25 +00002010
reed@google.comcabaf1d2012-01-11 21:03:05 +00002011 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002012 SkScalar cross = 0;
2013 if (kConvex_Convexity == conv) {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002014 // we loop, skipping over degenerate or flat segments that will
2015 // return 0 for the cross-product
reed@google.com69a99432012-01-10 18:00:10 +00002016 for (int i = 0; i < n - 2; ++i) {
2017 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
2018 if (cross) {
reed@google.comac8543f2012-01-30 20:51:25 +00002019 // early-exit, as kConvex is assumed to have only 1
2020 // non-degenerate contour
2021 return crossToDir(cross, dir);
reed@google.com69a99432012-01-10 18:00:10 +00002022 }
2023 }
2024 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002025 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002026 if (pts[index].fY < ymax) {
2027 continue;
2028 }
2029
reed@google.comc1ea60a2012-01-31 15:15:36 +00002030 // If there is more than 1 distinct point at the y-max, we take the
2031 // x-min and x-max of them and just subtract to compute the dir.
2032 if (pts[(index + 1) % n].fY == pts[index].fY) {
2033 int maxIndex;
2034 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002035 if (minIndex == maxIndex) {
2036 goto TRY_CROSSPROD;
2037 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002038 SkASSERT(pts[minIndex].fY == pts[index].fY);
2039 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2040 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2041 // we just subtract the indices, and let that auto-convert to
2042 // SkScalar, since we just want - or + to signal the direction.
2043 cross = minIndex - maxIndex;
2044 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002045 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002046 // Find a next and prev index to use for the cross-product test,
2047 // but we try to find pts that form non-zero vectors from pts[index]
2048 //
2049 // Its possible that we can't find two non-degenerate vectors, so
2050 // we have to guard our search (e.g. all the pts could be in the
2051 // same place).
2052
2053 // we pass n - 1 instead of -1 so we don't foul up % operator by
2054 // passing it a negative LH argument.
2055 int prev = find_diff_pt(pts, index, n, n - 1);
2056 if (prev == index) {
2057 // completely degenerate, skip to next contour
2058 continue;
2059 }
2060 int next = find_diff_pt(pts, index, n, 1);
2061 SkASSERT(next != index);
2062 cross = cross_prod(pts[prev], pts[index], pts[next]);
2063 // if we get a zero, but the pts aren't on top of each other, then
2064 // we can just look at the direction
2065 if (0 == cross) {
2066 // construct the subtract so we get the correct Direction below
2067 cross = pts[index].fX - pts[next].fX;
2068 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002069 }
reed@google.comac8543f2012-01-30 20:51:25 +00002070
2071 if (cross) {
2072 // record our best guess so far
2073 ymax = pts[index].fY;
2074 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002075 }
reed@google.com69a99432012-01-10 18:00:10 +00002076 }
2077 }
reed@google.com69a99432012-01-10 18:00:10 +00002078
reed@google.comac8543f2012-01-30 20:51:25 +00002079 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2080}