blob: 960d2ab83ac1ead75739e5295756b2da5ae149d0 [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
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000104SkPath::SkPath()
105 : fFillType(kWinding_FillType)
106 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000107 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000108 fSegmentMask = 0;
djsollen@google.com56c69772011-11-08 19:00:26 +0000109#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000110 fGenerationID = 0;
111#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000112}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
114SkPath::SkPath(const SkPath& src) {
115 SkDEBUGCODE(src.validate();)
116 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000117#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000118 // the assignment operator above increments the ID so correct for that here
119 fGenerationID--;
120#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121}
122
123SkPath::~SkPath() {
124 SkDEBUGCODE(this->validate();)
125}
126
127SkPath& SkPath::operator=(const SkPath& src) {
128 SkDEBUGCODE(src.validate();)
129
130 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000131 fBounds = src.fBounds;
132 fPts = src.fPts;
133 fVerbs = src.fVerbs;
134 fFillType = src.fFillType;
135 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000136 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000137 fSegmentMask = src.fSegmentMask;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000138 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 }
140 SkDEBUGCODE(this->validate();)
141 return *this;
142}
143
reed@android.com3abec1d2009-03-02 05:36:20 +0000144bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000145 // note: don't need to look at isConvex or bounds, since just comparing the
146 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000147
148 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
149 // since it is only a cache of info in the fVerbs, but its a fast way to
150 // notice a difference
151
reed@android.com3abec1d2009-03-02 05:36:20 +0000152 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000153 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
154 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000155}
156
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157void SkPath::swap(SkPath& other) {
158 SkASSERT(&other != NULL);
159
160 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000161 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 fPts.swap(other.fPts);
163 fVerbs.swap(other.fVerbs);
164 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000165 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000166 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000167 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000168 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 }
170}
171
djsollen@google.com56c69772011-11-08 19:00:26 +0000172#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000173uint32_t SkPath::getGenerationID() const {
174 return fGenerationID;
175}
176#endif
177
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178void SkPath::reset() {
179 SkDEBUGCODE(this->validate();)
180
181 fPts.reset();
182 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000183 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000184 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000185 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000186 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187}
188
189void SkPath::rewind() {
190 SkDEBUGCODE(this->validate();)
191
192 fPts.rewind();
193 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000194 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000195 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000196 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000197 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198}
199
200bool SkPath::isEmpty() const {
201 SkDEBUGCODE(this->validate();)
schenney@chromium.orge42b1d52011-12-21 19:13:51 +0000202#if SK_OLD_EMPTY_PATH_BEHAVIOR
203 int count = fVerbs.count();
204 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
205#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000206 return 0 == fVerbs.count();
schenney@chromium.orge42b1d52011-12-21 19:13:51 +0000207#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208}
209
caryclark@google.comf1316942011-07-26 19:54:45 +0000210/*
211 Determines if path is a rect by keeping track of changes in direction
212 and looking for a loop either clockwise or counterclockwise.
213
214 The direction is computed such that:
215 0: vertical up
216 1: horizontal right
217 2: vertical down
218 3: horizontal left
219
220A rectangle cycles up/right/down/left or up/left/down/right.
221
222The test fails if:
223 The path is closed, and followed by a line.
224 A second move creates a new endpoint.
225 A diagonal line is parsed.
226 There's more than four changes of direction.
227 There's a discontinuity on the line (e.g., a move in the middle)
228 The line reverses direction.
229 The rectangle doesn't complete a cycle.
230 The path contains a quadratic or cubic.
231 The path contains fewer than four points.
232 The final point isn't equal to the first point.
233
234It's OK if the path has:
235 Several colinear line segments composing a rectangle side.
236 Single points on the rectangle side.
237
238The direction takes advantage of the corners found since opposite sides
239must travel in opposite directions.
240
241FIXME: Allow colinear quads and cubics to be treated like lines.
242FIXME: If the API passes fill-only, return true if the filled stroke
243 is a rectangle, though the caller failed to close the path.
244 */
245bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000247
caryclark@google.comf1316942011-07-26 19:54:45 +0000248 int corners = 0;
249 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000250 first.set(0, 0);
251 last.set(0, 0);
252 int firstDirection = 0;
253 int lastDirection = 0;
254 int nextDirection = 0;
255 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000256 bool autoClose = false;
257 const uint8_t* verbs = fVerbs.begin();
258 const uint8_t* verbStop = fVerbs.end();
259 const SkPoint* pts = fPts.begin();
260 while (verbs != verbStop) {
261 switch (*verbs++) {
262 case kClose_Verb:
263 pts = fPts.begin();
264 autoClose = true;
265 case kLine_Verb: {
266 SkScalar left = last.fX;
267 SkScalar top = last.fY;
268 SkScalar right = pts->fX;
269 SkScalar bottom = pts->fY;
270 ++pts;
271 if (left != right && top != bottom) {
272 return false; // diagonal
273 }
274 if (left == right && top == bottom) {
275 break; // single point on side OK
276 }
277 nextDirection = (left != right) << 0 |
278 (left < right || top < bottom) << 1;
279 if (0 == corners) {
280 firstDirection = nextDirection;
281 first = last;
282 last = pts[-1];
283 corners = 1;
284 closedOrMoved = false;
285 break;
286 }
287 if (closedOrMoved) {
288 return false; // closed followed by a line
289 }
290 closedOrMoved = autoClose;
291 if (lastDirection != nextDirection) {
292 if (++corners > 4) {
293 return false; // too many direction changes
294 }
295 }
296 last = pts[-1];
297 if (lastDirection == nextDirection) {
298 break; // colinear segment
299 }
300 // Possible values for corners are 2, 3, and 4.
301 // When corners == 3, nextDirection opposes firstDirection.
302 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000303 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000304 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
305 if ((directionCycle ^ turn) != nextDirection) {
306 return false; // direction didn't follow cycle
307 }
308 break;
309 }
310 case kQuad_Verb:
311 case kCubic_Verb:
312 return false; // quadratic, cubic not allowed
313 case kMove_Verb:
314 last = *pts++;
315 closedOrMoved = true;
316 break;
317 }
318 lastDirection = nextDirection;
319 }
320 // Success if 4 corners and first point equals last
321 bool result = 4 == corners && first == last;
322 if (result && rect) {
323 *rect = getBounds();
324 }
325 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326}
327
328int SkPath::getPoints(SkPoint copy[], int max) const {
329 SkDEBUGCODE(this->validate();)
330
331 SkASSERT(max >= 0);
332 int count = fPts.count();
333 if (copy && max > 0 && count > 0) {
334 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
335 }
336 return count;
337}
338
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000339SkPoint SkPath::getPoint(int index) const {
340 if ((unsigned)index < (unsigned)fPts.count()) {
341 return fPts[index];
342 }
343 return SkPoint::Make(0, 0);
344}
345
reed@google.com294dd7b2011-10-11 11:58:32 +0000346bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 SkDEBUGCODE(this->validate();)
348
reed@google.com294dd7b2011-10-11 11:58:32 +0000349 int count = fPts.count();
350 if (count > 0) {
351 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 *lastPt = fPts[count - 1];
353 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000354 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000356 if (lastPt) {
357 lastPt->set(0, 0);
358 }
359 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360}
361
362void SkPath::setLastPt(SkScalar x, SkScalar y) {
363 SkDEBUGCODE(this->validate();)
364
365 int count = fPts.count();
366 if (count == 0) {
367 this->moveTo(x, y);
368 } else {
369 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000370 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371 }
372}
373
reed@android.comd252db02009-04-01 18:31:44 +0000374void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000376 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377
reed@android.comd252db02009-04-01 18:31:44 +0000378 fBoundsIsDirty = false;
379 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380}
381
reed@google.com04863fa2011-05-15 04:08:24 +0000382void SkPath::setConvexity(Convexity c) {
383 if (fConvexity != c) {
384 fConvexity = c;
385 GEN_ID_INC;
386 }
387}
388
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389//////////////////////////////////////////////////////////////////////////////
390// Construction methods
391
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000392#define DIRTY_AFTER_EDIT \
393 do { \
394 fBoundsIsDirty = true; \
395 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000396 } while (0)
397
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398void SkPath::incReserve(U16CPU inc) {
399 SkDEBUGCODE(this->validate();)
400
401 fVerbs.setReserve(fVerbs.count() + inc);
402 fPts.setReserve(fPts.count() + inc);
403
404 SkDEBUGCODE(this->validate();)
405}
406
407void SkPath::moveTo(SkScalar x, SkScalar y) {
408 SkDEBUGCODE(this->validate();)
409
410 int vc = fVerbs.count();
411 SkPoint* pt;
412
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000413#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
414 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
415 pt = &fPts[fPts.count() - 1];
416 } else {
417 pt = fPts.append();
418 *fVerbs.append() = kMove_Verb;
419 }
420#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000421 pt = fPts.append();
422 *fVerbs.append() = kMove_Verb;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000423#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 pt->set(x, y);
425
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000426 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000427 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428}
429
430void SkPath::rMoveTo(SkScalar x, SkScalar y) {
431 SkPoint pt;
432 this->getLastPt(&pt);
433 this->moveTo(pt.fX + x, pt.fY + y);
434}
435
436void SkPath::lineTo(SkScalar x, SkScalar y) {
437 SkDEBUGCODE(this->validate();)
438
439 if (fVerbs.count() == 0) {
440 fPts.append()->set(0, 0);
441 *fVerbs.append() = kMove_Verb;
442 }
443 fPts.append()->set(x, y);
444 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000445 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000447 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000448 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449}
450
451void SkPath::rLineTo(SkScalar x, SkScalar y) {
452 SkPoint pt;
453 this->getLastPt(&pt);
454 this->lineTo(pt.fX + x, pt.fY + y);
455}
456
457void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
458 SkDEBUGCODE(this->validate();)
459
460 if (fVerbs.count() == 0) {
461 fPts.append()->set(0, 0);
462 *fVerbs.append() = kMove_Verb;
463 }
464
465 SkPoint* pts = fPts.append(2);
466 pts[0].set(x1, y1);
467 pts[1].set(x2, y2);
468 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000469 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000471 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000472 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473}
474
475void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
476 SkPoint pt;
477 this->getLastPt(&pt);
478 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
479}
480
481void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
482 SkScalar x3, SkScalar y3) {
483 SkDEBUGCODE(this->validate();)
484
485 if (fVerbs.count() == 0) {
486 fPts.append()->set(0, 0);
487 *fVerbs.append() = kMove_Verb;
488 }
489 SkPoint* pts = fPts.append(3);
490 pts[0].set(x1, y1);
491 pts[1].set(x2, y2);
492 pts[2].set(x3, y3);
493 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000494 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000495
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000496 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000497 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498}
499
500void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
501 SkScalar x3, SkScalar y3) {
502 SkPoint pt;
503 this->getLastPt(&pt);
504 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
505 pt.fX + x3, pt.fY + y3);
506}
507
508void SkPath::close() {
509 SkDEBUGCODE(this->validate();)
510
511 int count = fVerbs.count();
512 if (count > 0) {
513 switch (fVerbs[count - 1]) {
514 case kLine_Verb:
515 case kQuad_Verb:
516 case kCubic_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000517#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000518 case kMove_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000519#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000521 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 break;
523 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000524 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525 break;
526 }
527 }
528}
529
530///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000531
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532void SkPath::addRect(const SkRect& rect, Direction dir) {
533 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
534}
535
536void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
537 SkScalar bottom, Direction dir) {
538 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
539
540 this->incReserve(5);
541
542 this->moveTo(left, top);
543 if (dir == kCCW_Direction) {
544 this->lineTo(left, bottom);
545 this->lineTo(right, bottom);
546 this->lineTo(right, top);
547 } else {
548 this->lineTo(right, top);
549 this->lineTo(right, bottom);
550 this->lineTo(left, bottom);
551 }
552 this->close();
553}
554
555#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
556
557void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
558 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 SkScalar w = rect.width();
560 SkScalar halfW = SkScalarHalf(w);
561 SkScalar h = rect.height();
562 SkScalar halfH = SkScalarHalf(h);
563
564 if (halfW <= 0 || halfH <= 0) {
565 return;
566 }
567
reed@google.comabf15c12011-01-18 20:35:51 +0000568 bool skip_hori = rx >= halfW;
569 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570
571 if (skip_hori && skip_vert) {
572 this->addOval(rect, dir);
573 return;
574 }
reed@google.comabf15c12011-01-18 20:35:51 +0000575
576 SkAutoPathBoundsUpdate apbu(this, rect);
577
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 if (skip_hori) {
579 rx = halfW;
580 } else if (skip_vert) {
581 ry = halfH;
582 }
583
584 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
585 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
586
587 this->incReserve(17);
588 this->moveTo(rect.fRight - rx, rect.fTop);
589 if (dir == kCCW_Direction) {
590 if (!skip_hori) {
591 this->lineTo(rect.fLeft + rx, rect.fTop); // top
592 }
593 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
594 rect.fLeft, rect.fTop + ry - sy,
595 rect.fLeft, rect.fTop + ry); // top-left
596 if (!skip_vert) {
597 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
598 }
599 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
600 rect.fLeft + rx - sx, rect.fBottom,
601 rect.fLeft + rx, rect.fBottom); // bot-left
602 if (!skip_hori) {
603 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
604 }
605 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
606 rect.fRight, rect.fBottom - ry + sy,
607 rect.fRight, rect.fBottom - ry); // bot-right
608 if (!skip_vert) {
609 this->lineTo(rect.fRight, rect.fTop + ry);
610 }
611 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
612 rect.fRight - rx + sx, rect.fTop,
613 rect.fRight - rx, rect.fTop); // top-right
614 } else {
615 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
616 rect.fRight, rect.fTop + ry - sy,
617 rect.fRight, rect.fTop + ry); // top-right
618 if (!skip_vert) {
619 this->lineTo(rect.fRight, rect.fBottom - ry);
620 }
621 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
622 rect.fRight - rx + sx, rect.fBottom,
623 rect.fRight - rx, rect.fBottom); // bot-right
624 if (!skip_hori) {
625 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
626 }
627 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
628 rect.fLeft, rect.fBottom - ry + sy,
629 rect.fLeft, rect.fBottom - ry); // bot-left
630 if (!skip_vert) {
631 this->lineTo(rect.fLeft, rect.fTop + ry); // left
632 }
633 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
634 rect.fLeft + rx - sx, rect.fTop,
635 rect.fLeft + rx, rect.fTop); // top-left
636 if (!skip_hori) {
637 this->lineTo(rect.fRight - rx, rect.fTop); // top
638 }
639 }
640 this->close();
641}
642
643static void add_corner_arc(SkPath* path, const SkRect& rect,
644 SkScalar rx, SkScalar ry, int startAngle,
645 SkPath::Direction dir, bool forceMoveTo) {
646 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
647 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000648
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 SkRect r;
650 r.set(-rx, -ry, rx, ry);
651
652 switch (startAngle) {
653 case 0:
654 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
655 break;
656 case 90:
657 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
658 break;
659 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
660 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000661 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 }
reed@google.comabf15c12011-01-18 20:35:51 +0000663
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkScalar start = SkIntToScalar(startAngle);
665 SkScalar sweep = SkIntToScalar(90);
666 if (SkPath::kCCW_Direction == dir) {
667 start += sweep;
668 sweep = -sweep;
669 }
reed@google.comabf15c12011-01-18 20:35:51 +0000670
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671 path->arcTo(r, start, sweep, forceMoveTo);
672}
673
674void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
675 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000676 // abort before we invoke SkAutoPathBoundsUpdate()
677 if (rect.isEmpty()) {
678 return;
679 }
680
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681 SkAutoPathBoundsUpdate apbu(this, rect);
682
683 if (kCW_Direction == dir) {
684 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
685 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
686 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
687 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
688 } else {
689 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
690 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
691 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
692 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
693 }
694 this->close();
695}
696
697void SkPath::addOval(const SkRect& oval, Direction dir) {
698 SkAutoPathBoundsUpdate apbu(this, oval);
699
700 SkScalar cx = oval.centerX();
701 SkScalar cy = oval.centerY();
702 SkScalar rx = SkScalarHalf(oval.width());
703 SkScalar ry = SkScalarHalf(oval.height());
704#if 0 // these seem faster than using quads (1/2 the number of edges)
705 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
706 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
707
708 this->incReserve(13);
709 this->moveTo(cx + rx, cy);
710 if (dir == kCCW_Direction) {
711 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
712 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
713 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
714 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
715 } else {
716 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
717 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
718 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
719 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
720 }
721#else
722 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
723 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
724 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
725 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
726
727 /*
728 To handle imprecision in computing the center and radii, we revert to
729 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
730 to ensure that we don't exceed the oval's bounds *ever*, since we want
731 to use oval for our fast-bounds, rather than have to recompute it.
732 */
733 const SkScalar L = oval.fLeft; // cx - rx
734 const SkScalar T = oval.fTop; // cy - ry
735 const SkScalar R = oval.fRight; // cx + rx
736 const SkScalar B = oval.fBottom; // cy + ry
737
738 this->incReserve(17); // 8 quads + close
739 this->moveTo(R, cy);
740 if (dir == kCCW_Direction) {
741 this->quadTo( R, cy - sy, cx + mx, cy - my);
742 this->quadTo(cx + sx, T, cx , T);
743 this->quadTo(cx - sx, T, cx - mx, cy - my);
744 this->quadTo( L, cy - sy, L, cy );
745 this->quadTo( L, cy + sy, cx - mx, cy + my);
746 this->quadTo(cx - sx, B, cx , B);
747 this->quadTo(cx + sx, B, cx + mx, cy + my);
748 this->quadTo( R, cy + sy, R, cy );
749 } else {
750 this->quadTo( R, cy + sy, cx + mx, cy + my);
751 this->quadTo(cx + sx, B, cx , B);
752 this->quadTo(cx - sx, B, cx - mx, cy + my);
753 this->quadTo( L, cy + sy, L, cy );
754 this->quadTo( L, cy - sy, cx - mx, cy - my);
755 this->quadTo(cx - sx, T, cx , T);
756 this->quadTo(cx + sx, T, cx + mx, cy - my);
757 this->quadTo( R, cy - sy, R, cy );
758 }
759#endif
760 this->close();
761}
762
763void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
764 if (r > 0) {
765 SkRect rect;
766 rect.set(x - r, y - r, x + r, y + r);
767 this->addOval(rect, dir);
768 }
769}
770
771#include "SkGeometry.h"
772
773static int build_arc_points(const SkRect& oval, SkScalar startAngle,
774 SkScalar sweepAngle,
775 SkPoint pts[kSkBuildQuadArcStorage]) {
776 SkVector start, stop;
777
778 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
779 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
780 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000781
782 /* If the sweep angle is nearly (but less than) 360, then due to precision
783 loss in radians-conversion and/or sin/cos, we may end up with coincident
784 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
785 of drawing a nearly complete circle (good).
786 e.g. canvas.drawArc(0, 359.99, ...)
787 -vs- canvas.drawArc(0, 359.9, ...)
788 We try to detect this edge case, and tweak the stop vector
789 */
790 if (start == stop) {
791 SkScalar sw = SkScalarAbs(sweepAngle);
792 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
793 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
794 // make a guess at a tiny angle (in radians) to tweak by
795 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
796 // not sure how much will be enough, so we use a loop
797 do {
798 stopRad -= deltaRad;
799 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
800 } while (start == stop);
801 }
802 }
803
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000805
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
807 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000808
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 return SkBuildQuadArc(start, stop,
810 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
811 &matrix, pts);
812}
813
814void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
815 bool forceMoveTo) {
816 if (oval.width() < 0 || oval.height() < 0) {
817 return;
818 }
819
820 SkPoint pts[kSkBuildQuadArcStorage];
821 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
822 SkASSERT((count & 1) == 1);
823
824 if (fVerbs.count() == 0) {
825 forceMoveTo = true;
826 }
827 this->incReserve(count);
828 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
829 for (int i = 1; i < count; i += 2) {
830 this->quadTo(pts[i], pts[i+1]);
831 }
832}
833
834void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
835 SkScalar sweepAngle) {
836 if (oval.isEmpty() || 0 == sweepAngle) {
837 return;
838 }
839
840 const SkScalar kFullCircleAngle = SkIntToScalar(360);
841
842 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
843 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
844 return;
845 }
846
847 SkPoint pts[kSkBuildQuadArcStorage];
848 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
849
850 this->incReserve(count);
851 this->moveTo(pts[0]);
852 for (int i = 1; i < count; i += 2) {
853 this->quadTo(pts[i], pts[i+1]);
854 }
855}
856
857/*
858 Need to handle the case when the angle is sharp, and our computed end-points
859 for the arc go behind pt1 and/or p2...
860*/
861void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
862 SkScalar radius) {
863 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 // need to know our prev pt so we can construct tangent vectors
866 {
867 SkPoint start;
868 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000869 // Handle degenerate cases by adding a line to the first point and
870 // bailing out.
871 if ((x1 == start.fX && y1 == start.fY) ||
872 (x1 == x2 && y1 == y2) ||
873 radius == 0) {
874 this->lineTo(x1, y1);
875 return;
876 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 before.setNormalize(x1 - start.fX, y1 - start.fY);
878 after.setNormalize(x2 - x1, y2 - y1);
879 }
reed@google.comabf15c12011-01-18 20:35:51 +0000880
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 SkScalar cosh = SkPoint::DotProduct(before, after);
882 SkScalar sinh = SkPoint::CrossProduct(before, after);
883
884 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000885 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 return;
887 }
reed@google.comabf15c12011-01-18 20:35:51 +0000888
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
890 if (dist < 0) {
891 dist = -dist;
892 }
893
894 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
895 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
896 SkRotationDirection arcDir;
897
898 // now turn before/after into normals
899 if (sinh > 0) {
900 before.rotateCCW();
901 after.rotateCCW();
902 arcDir = kCW_SkRotationDirection;
903 } else {
904 before.rotateCW();
905 after.rotateCW();
906 arcDir = kCCW_SkRotationDirection;
907 }
908
909 SkMatrix matrix;
910 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000911
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 matrix.setScale(radius, radius);
913 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
914 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000915
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000917
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 this->incReserve(count);
919 // [xx,yy] == pts[0]
920 this->lineTo(xx, yy);
921 for (int i = 1; i < count; i += 2) {
922 this->quadTo(pts[i], pts[i+1]);
923 }
924}
925
926///////////////////////////////////////////////////////////////////////////////
927
928void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
929 SkMatrix matrix;
930
931 matrix.setTranslate(dx, dy);
932 this->addPath(path, matrix);
933}
934
935void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
936 this->incReserve(path.fPts.count());
937
schenney@chromium.org6630d8d2012-01-04 21:05:51 +0000938 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000939 SkPoint pts[4];
940 Verb verb;
941
942 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
943
944 while ((verb = iter.next(pts)) != kDone_Verb) {
945 switch (verb) {
946 case kMove_Verb:
947 proc(matrix, &pts[0], &pts[0], 1);
948 this->moveTo(pts[0]);
949 break;
950 case kLine_Verb:
951 proc(matrix, &pts[1], &pts[1], 1);
952 this->lineTo(pts[1]);
953 break;
954 case kQuad_Verb:
955 proc(matrix, &pts[1], &pts[1], 2);
956 this->quadTo(pts[1], pts[2]);
957 break;
958 case kCubic_Verb:
959 proc(matrix, &pts[1], &pts[1], 3);
960 this->cubicTo(pts[1], pts[2], pts[3]);
961 break;
962 case kClose_Verb:
963 this->close();
964 break;
965 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000966 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 }
968 }
969}
970
971///////////////////////////////////////////////////////////////////////////////
972
973static const uint8_t gPtsInVerb[] = {
974 1, // kMove
975 1, // kLine
976 2, // kQuad
977 3, // kCubic
978 0, // kClose
979 0 // kDone
980};
981
982// ignore the initial moveto, and stop when the 1st contour ends
983void SkPath::pathTo(const SkPath& path) {
984 int i, vcount = path.fVerbs.count();
985 if (vcount == 0) {
986 return;
987 }
988
989 this->incReserve(vcount);
990
991 const uint8_t* verbs = path.fVerbs.begin();
992 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
993
994 SkASSERT(verbs[0] == kMove_Verb);
995 for (i = 1; i < vcount; i++) {
996 switch (verbs[i]) {
997 case kLine_Verb:
998 this->lineTo(pts[0].fX, pts[0].fY);
999 break;
1000 case kQuad_Verb:
1001 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1002 break;
1003 case kCubic_Verb:
1004 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1005 pts[2].fX, pts[2].fY);
1006 break;
1007 case kClose_Verb:
1008 return;
1009 }
1010 pts += gPtsInVerb[verbs[i]];
1011 }
1012}
1013
1014// ignore the last point of the 1st contour
1015void SkPath::reversePathTo(const SkPath& path) {
1016 int i, vcount = path.fVerbs.count();
1017 if (vcount == 0) {
1018 return;
1019 }
1020
1021 this->incReserve(vcount);
1022
1023 const uint8_t* verbs = path.fVerbs.begin();
1024 const SkPoint* pts = path.fPts.begin();
1025
1026 SkASSERT(verbs[0] == kMove_Verb);
1027 for (i = 1; i < vcount; i++) {
1028 int n = gPtsInVerb[verbs[i]];
1029 if (n == 0) {
1030 break;
1031 }
1032 pts += n;
1033 }
1034
1035 while (--i > 0) {
1036 switch (verbs[i]) {
1037 case kLine_Verb:
1038 this->lineTo(pts[-1].fX, pts[-1].fY);
1039 break;
1040 case kQuad_Verb:
1041 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1042 break;
1043 case kCubic_Verb:
1044 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1045 pts[-3].fX, pts[-3].fY);
1046 break;
1047 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001048 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 break;
1050 }
1051 pts -= gPtsInVerb[verbs[i]];
1052 }
1053}
1054
reed@google.com63d73742012-01-10 15:33:12 +00001055void SkPath::reverseAddPath(const SkPath& src) {
1056 this->incReserve(src.fPts.count());
1057
1058 const SkPoint* startPts = src.fPts.begin();
1059 const SkPoint* pts = src.fPts.end();
1060 const uint8_t* startVerbs = src.fVerbs.begin();
1061 const uint8_t* verbs = src.fVerbs.end();
1062
1063 bool needMove = true;
1064 bool needClose = false;
1065 while (verbs > startVerbs) {
1066 uint8_t v = *--verbs;
1067 int n = gPtsInVerb[v];
1068
1069 if (needMove) {
1070 --pts;
1071 this->moveTo(pts->fX, pts->fY);
1072 needMove = false;
1073 }
1074 pts -= n;
1075 switch (v) {
1076 case kMove_Verb:
1077 if (needClose) {
1078 this->close();
1079 needClose = false;
1080 }
1081 needMove = true;
1082 pts += 1; // so we see the point in "if (needMove)" above
1083 break;
1084 case kLine_Verb:
1085 this->lineTo(pts[0]);
1086 break;
1087 case kQuad_Verb:
1088 this->quadTo(pts[1], pts[0]);
1089 break;
1090 case kCubic_Verb:
1091 this->cubicTo(pts[2], pts[1], pts[0]);
1092 break;
1093 case kClose_Verb:
1094 needClose = true;
1095 break;
1096 default:
1097 SkASSERT(!"unexpected verb");
1098 }
1099 }
1100}
1101
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102///////////////////////////////////////////////////////////////////////////////
1103
1104void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1105 SkMatrix matrix;
1106
1107 matrix.setTranslate(dx, dy);
1108 this->transform(matrix, dst);
1109}
1110
1111#include "SkGeometry.h"
1112
1113static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1114 int level = 2) {
1115 if (--level >= 0) {
1116 SkPoint tmp[5];
1117
1118 SkChopQuadAtHalf(pts, tmp);
1119 subdivide_quad_to(path, &tmp[0], level);
1120 subdivide_quad_to(path, &tmp[2], level);
1121 } else {
1122 path->quadTo(pts[1], pts[2]);
1123 }
1124}
1125
1126static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1127 int level = 2) {
1128 if (--level >= 0) {
1129 SkPoint tmp[7];
1130
1131 SkChopCubicAtHalf(pts, tmp);
1132 subdivide_cubic_to(path, &tmp[0], level);
1133 subdivide_cubic_to(path, &tmp[3], level);
1134 } else {
1135 path->cubicTo(pts[1], pts[2], pts[3]);
1136 }
1137}
1138
1139void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1140 SkDEBUGCODE(this->validate();)
1141 if (dst == NULL) {
1142 dst = (SkPath*)this;
1143 }
1144
tomhudson@google.com8d430182011-06-06 19:11:19 +00001145 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 SkPath tmp;
1147 tmp.fFillType = fFillType;
1148
1149 SkPath::Iter iter(*this, false);
1150 SkPoint pts[4];
1151 SkPath::Verb verb;
1152
1153 while ((verb = iter.next(pts)) != kDone_Verb) {
1154 switch (verb) {
1155 case kMove_Verb:
1156 tmp.moveTo(pts[0]);
1157 break;
1158 case kLine_Verb:
1159 tmp.lineTo(pts[1]);
1160 break;
1161 case kQuad_Verb:
1162 subdivide_quad_to(&tmp, pts);
1163 break;
1164 case kCubic_Verb:
1165 subdivide_cubic_to(&tmp, pts);
1166 break;
1167 case kClose_Verb:
1168 tmp.close();
1169 break;
1170 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001171 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172 break;
1173 }
1174 }
1175
1176 dst->swap(tmp);
1177 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1178 } else {
1179 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001180 // fBoundsIsDirty before we set it
1181 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001183 matrix.mapRect(&dst->fBounds, fBounds);
1184 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001185 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001186 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001187 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 }
1189
1190 if (this != dst) {
1191 dst->fVerbs = fVerbs;
1192 dst->fPts.setCount(fPts.count());
1193 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001194 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001195 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001196 }
1197 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1198 SkDEBUGCODE(dst->validate();)
1199 }
1200}
1201
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202///////////////////////////////////////////////////////////////////////////////
1203///////////////////////////////////////////////////////////////////////////////
1204
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001205enum SegmentState {
1206 kAfterClose_SegmentState, // We will need a move next, but we have a
1207 // previous close pt to use for the new move.
1208 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1209 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1210 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211};
1212
1213SkPath::Iter::Iter() {
1214#ifdef SK_DEBUG
1215 fPts = NULL;
1216 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001217 fForceClose = fCloseLine = false;
1218 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219#endif
1220 // need to init enough to make next() harmlessly return kDone_Verb
1221 fVerbs = NULL;
1222 fVerbStop = NULL;
1223 fNeedClose = false;
1224}
1225
1226SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1227 this->setPath(path, forceClose);
1228}
1229
1230void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1231 fPts = path.fPts.begin();
1232 fVerbs = path.fVerbs.begin();
1233 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001234 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001235 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 fForceClose = SkToU8(forceClose);
1237 fNeedClose = false;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001238 fSegmentState = kAfterClose_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239}
1240
1241bool SkPath::Iter::isClosedContour() const {
1242 if (fVerbs == NULL || fVerbs == fVerbStop) {
1243 return false;
1244 }
1245 if (fForceClose) {
1246 return true;
1247 }
1248
1249 const uint8_t* verbs = fVerbs;
1250 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001251
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 if (kMove_Verb == *verbs) {
1253 verbs += 1; // skip the initial moveto
1254 }
1255
1256 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001257 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 if (kMove_Verb == v) {
1259 break;
1260 }
1261 if (kClose_Verb == v) {
1262 return true;
1263 }
1264 }
1265 return false;
1266}
1267
1268SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1269 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001270 // A special case: if both points are NaN, SkPoint::operation== returns
1271 // false, but the iterator expects that they are treated as the same.
1272 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001273 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1274 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001275 return kClose_Verb;
1276 }
1277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 if (pts) {
1279 pts[0] = fLastPt;
1280 pts[1] = fMoveTo;
1281 }
1282 fLastPt = fMoveTo;
1283 fCloseLine = true;
1284 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001285 } else {
1286 pts[0] = fMoveTo;
1287 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289}
1290
1291bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001292 if (fSegmentState == kAfterClose_SegmentState) {
1293 // We have closed a curve and have a primitive, so we need a move.
1294 // Set the first return pt to the most recent move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 if (pts) {
1296 *pts = fMoveTo;
1297 }
1298 fNeedClose = fForceClose;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001299 fSegmentState = kAfterMove_SegmentState;
1300 fVerbs -= 1; // Step back to see the primitive again
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 return true;
1302 }
1303
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001304 if (fSegmentState == kAfterMove_SegmentState) {
1305 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 if (pts) {
1307 *pts = fMoveTo;
1308 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001309 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001311 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1312 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 if (pts) {
1314 *pts = fPts[-1];
1315 }
1316 }
1317 return false;
1318}
1319
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001320void SkPath::Iter::consumeDegenerateSegments() {
1321 // We need to step over anything that will not move the current draw point
1322 // forward before the next move is seen
1323 const uint8_t* lastMoveVerb = 0;
1324 const SkPoint* lastMovePt = 0;
1325 SkPoint lastPt = fLastPt;
1326 while (fVerbs != fVerbStop) {
1327 unsigned verb = *fVerbs;
1328 switch (verb) {
1329 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001330 // Keep a record of this most recent move
1331 lastMoveVerb = fVerbs;
1332 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001333 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001334 fVerbs++;
1335 fPts++;
1336 break;
1337
1338 case kClose_Verb:
1339 // A close when we are in a segment is always valid
1340 if (fSegmentState == kAfterPrimitive_SegmentState) {
1341 return;
1342 }
1343 // A close at any other time must be ignored
1344 fVerbs++;
1345 break;
1346
1347 case kLine_Verb:
1348 if (!IsLineDegenerate(lastPt, fPts[0])) {
1349 if (lastMoveVerb) {
1350 fVerbs = lastMoveVerb;
1351 fPts = lastMovePt;
1352 return;
1353 }
1354 return;
1355 }
1356 // Ignore this line and continue
1357 fVerbs++;
1358 fPts++;
1359 break;
1360
1361 case kQuad_Verb:
1362 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1363 if (lastMoveVerb) {
1364 fVerbs = lastMoveVerb;
1365 fPts = lastMovePt;
1366 return;
1367 }
1368 return;
1369 }
1370 // Ignore this line and continue
1371 fVerbs++;
1372 fPts += 2;
1373 break;
1374
1375 case kCubic_Verb:
1376 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1377 if (lastMoveVerb) {
1378 fVerbs = lastMoveVerb;
1379 fPts = lastMovePt;
1380 return;
1381 }
1382 return;
1383 }
1384 // Ignore this line and continue
1385 fVerbs++;
1386 fPts += 3;
1387 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001388
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001389 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001390 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001391 }
1392 }
1393}
1394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001396#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001397 this->consumeDegenerateSegments();
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001398#endif
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001399
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001401 // Close the curve if requested and if there is some curve to close
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001402#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1403 if (fNeedClose) {
1404#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001405 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001406#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 if (kLine_Verb == this->autoClose(pts)) {
1408 return kLine_Verb;
1409 }
1410 fNeedClose = false;
1411 return kClose_Verb;
1412 }
1413 return kDone_Verb;
1414 }
1415
1416 unsigned verb = *fVerbs++;
1417 const SkPoint* srcPts = fPts;
1418
1419 switch (verb) {
1420 case kMove_Verb:
1421 if (fNeedClose) {
1422 fVerbs -= 1;
1423 verb = this->autoClose(pts);
1424 if (verb == kClose_Verb) {
1425 fNeedClose = false;
1426 }
1427 return (Verb)verb;
1428 }
1429 if (fVerbs == fVerbStop) { // might be a trailing moveto
1430 return kDone_Verb;
1431 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001432 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 if (pts) {
1434 pts[0] = *srcPts;
1435 }
1436 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001437 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001438#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001439 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001440#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 fNeedClose = fForceClose;
1442 break;
1443 case kLine_Verb:
1444 if (this->cons_moveTo(pts)) {
1445 return kMove_Verb;
1446 }
1447 if (pts) {
1448 pts[1] = srcPts[0];
1449 }
1450 fLastPt = srcPts[0];
1451 fCloseLine = false;
1452 srcPts += 1;
1453 break;
1454 case kQuad_Verb:
1455 if (this->cons_moveTo(pts)) {
1456 return kMove_Verb;
1457 }
1458 if (pts) {
1459 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1460 }
1461 fLastPt = srcPts[1];
1462 srcPts += 2;
1463 break;
1464 case kCubic_Verb:
1465 if (this->cons_moveTo(pts)) {
1466 return kMove_Verb;
1467 }
1468 if (pts) {
1469 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1470 }
1471 fLastPt = srcPts[2];
1472 srcPts += 3;
1473 break;
1474 case kClose_Verb:
1475 verb = this->autoClose(pts);
1476 if (verb == kLine_Verb) {
1477 fVerbs -= 1;
1478 } else {
1479 fNeedClose = false;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001480#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001481 fSegmentState = kAfterClose_SegmentState;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001482#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001484#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1485 fSegmentState = kAfterClose_SegmentState;
1486#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001487 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001488#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 break;
1490 }
1491 fPts = srcPts;
1492 return (Verb)verb;
1493}
1494
1495///////////////////////////////////////////////////////////////////////////////
1496
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001497SkPath::RawIter::RawIter() {
1498#ifdef SK_DEBUG
1499 fPts = NULL;
1500 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1501#endif
1502 // need to init enough to make next() harmlessly return kDone_Verb
1503 fVerbs = NULL;
1504 fVerbStop = NULL;
1505}
1506
1507SkPath::RawIter::RawIter(const SkPath& path) {
1508 this->setPath(path);
1509}
1510
1511void SkPath::RawIter::setPath(const SkPath& path) {
1512 fPts = path.fPts.begin();
1513 fVerbs = path.fVerbs.begin();
1514 fVerbStop = path.fVerbs.end();
1515 fMoveTo.fX = fMoveTo.fY = 0;
1516 fLastPt.fX = fLastPt.fY = 0;
1517}
1518
1519SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
1520 if (fVerbs == fVerbStop) {
1521 return kDone_Verb;
1522 }
1523
1524 unsigned verb = *fVerbs++;
1525 const SkPoint* srcPts = fPts;
1526
1527 switch (verb) {
1528 case kMove_Verb:
1529 if (pts) {
1530 pts[0] = *srcPts;
1531 }
1532 fMoveTo = srcPts[0];
1533 fLastPt = fMoveTo;
1534 srcPts += 1;
1535 break;
1536 case kLine_Verb:
1537 if (pts) {
1538 pts[0] = fLastPt;
1539 pts[1] = srcPts[0];
1540 }
1541 fLastPt = srcPts[0];
1542 srcPts += 1;
1543 break;
1544 case kQuad_Verb:
1545 if (pts) {
1546 pts[0] = fLastPt;
1547 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1548 }
1549 fLastPt = srcPts[1];
1550 srcPts += 2;
1551 break;
1552 case kCubic_Verb:
1553 if (pts) {
1554 pts[0] = fLastPt;
1555 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1556 }
1557 fLastPt = srcPts[2];
1558 srcPts += 3;
1559 break;
1560 case kClose_Verb:
1561 fLastPt = fMoveTo;
1562 if (pts) {
1563 pts[0] = fMoveTo;
1564 }
1565 break;
1566 }
1567 fPts = srcPts;
1568 return (Verb)verb;
1569}
1570
1571///////////////////////////////////////////////////////////////////////////////
1572
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573/*
1574 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1575*/
1576
reed@google.com73945652011-04-25 19:04:27 +00001577void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 SkDEBUGCODE(this->validate();)
1579
1580 buffer.write32(fPts.count());
1581 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001582 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1584 buffer.writePad(fVerbs.begin(), fVerbs.count());
1585}
1586
reed@google.com73945652011-04-25 19:04:27 +00001587void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 fPts.setCount(buffer.readS32());
1589 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001590 uint32_t packed = buffer.readS32();
1591 fFillType = packed >> 8;
1592 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1594 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001595
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001596 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001597 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598
1599 SkDEBUGCODE(this->validate();)
1600}
1601
1602///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604void SkPath::dump(bool forceClose, const char title[]) const {
1605 Iter iter(*this, forceClose);
1606 SkPoint pts[4];
1607 Verb verb;
1608
1609 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1610 title ? title : "");
1611
1612 while ((verb = iter.next(pts)) != kDone_Verb) {
1613 switch (verb) {
1614 case kMove_Verb:
1615#ifdef SK_CAN_USE_FLOAT
1616 SkDebugf(" path: moveTo [%g %g]\n",
1617 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1618#else
1619 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1620#endif
1621 break;
1622 case kLine_Verb:
1623#ifdef SK_CAN_USE_FLOAT
1624 SkDebugf(" path: lineTo [%g %g]\n",
1625 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1626#else
1627 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1628#endif
1629 break;
1630 case kQuad_Verb:
1631#ifdef SK_CAN_USE_FLOAT
1632 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1633 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1634 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1635#else
1636 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1637 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1638#endif
1639 break;
1640 case kCubic_Verb:
1641#ifdef SK_CAN_USE_FLOAT
1642 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1643 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1644 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1645 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1646#else
1647 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1648 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1649 pts[3].fX, pts[3].fY);
1650#endif
1651 break;
1652 case kClose_Verb:
1653 SkDebugf(" path: close\n");
1654 break;
1655 default:
1656 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1657 verb = kDone_Verb; // stop the loop
1658 break;
1659 }
1660 }
1661 SkDebugf("path: done %s\n", title ? title : "");
1662}
1663
reed@android.come522ca52009-11-23 20:10:41 +00001664void SkPath::dump() const {
1665 this->dump(false);
1666}
1667
1668#ifdef SK_DEBUG
1669void SkPath::validate() const {
1670 SkASSERT(this != NULL);
1671 SkASSERT((fFillType & ~3) == 0);
1672 fPts.validate();
1673 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001674
reed@android.come522ca52009-11-23 20:10:41 +00001675 if (!fBoundsIsDirty) {
1676 SkRect bounds;
1677 compute_pt_bounds(&bounds, fPts);
1678 if (fPts.count() <= 1) {
1679 // if we're empty, fBounds may be empty but translated, so we can't
1680 // necessarily compare to bounds directly
1681 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1682 // be [2, 2, 2, 2]
1683 SkASSERT(bounds.isEmpty());
1684 SkASSERT(fBounds.isEmpty());
1685 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001686 if (bounds.isEmpty()) {
1687 SkASSERT(fBounds.isEmpty());
1688 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001689 if (!fBounds.isEmpty()) {
1690 SkASSERT(fBounds.contains(bounds));
1691 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001692 }
reed@android.come522ca52009-11-23 20:10:41 +00001693 }
1694 }
reed@google.com10296cc2011-09-21 12:29:05 +00001695
1696 uint32_t mask = 0;
1697 for (int i = 0; i < fVerbs.count(); i++) {
1698 switch (fVerbs[i]) {
1699 case kLine_Verb:
1700 mask |= kLine_SegmentMask;
1701 break;
1702 case kQuad_Verb:
1703 mask |= kQuad_SegmentMask;
1704 break;
1705 case kCubic_Verb:
1706 mask |= kCubic_SegmentMask;
1707 }
1708 }
1709 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001710}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711#endif
reed@android.come522ca52009-11-23 20:10:41 +00001712
reed@google.com04863fa2011-05-15 04:08:24 +00001713///////////////////////////////////////////////////////////////////////////////
1714
reed@google.com0b7b9822011-05-16 12:29:27 +00001715static int sign(SkScalar x) { return x < 0; }
1716#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001717
reed@google.com04863fa2011-05-15 04:08:24 +00001718static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001719 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001720}
1721
1722// only valid for a single contour
1723struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001724 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001725 fSign = 0;
1726 // warnings
1727 fCurrPt.set(0, 0);
1728 fVec0.set(0, 0);
1729 fVec1.set(0, 0);
1730 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001731
1732 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001733 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001734 }
1735
1736 SkPath::Convexity getConvexity() const { return fConvexity; }
1737
1738 void addPt(const SkPoint& pt) {
1739 if (SkPath::kConcave_Convexity == fConvexity) {
1740 return;
1741 }
1742
1743 if (0 == fPtCount) {
1744 fCurrPt = pt;
1745 ++fPtCount;
1746 } else {
1747 SkVector vec = pt - fCurrPt;
1748 if (vec.fX || vec.fY) {
1749 fCurrPt = pt;
1750 if (++fPtCount == 2) {
1751 fFirstVec = fVec1 = vec;
1752 } else {
1753 SkASSERT(fPtCount > 2);
1754 this->addVec(vec);
1755 }
reed@google.com85b6e392011-05-15 20:25:17 +00001756
1757 int sx = sign(vec.fX);
1758 int sy = sign(vec.fY);
1759 fDx += (sx != fSx);
1760 fDy += (sy != fSy);
1761 fSx = sx;
1762 fSy = sy;
1763
1764 if (fDx > 3 || fDy > 3) {
1765 fConvexity = SkPath::kConcave_Convexity;
1766 }
reed@google.com04863fa2011-05-15 04:08:24 +00001767 }
1768 }
1769 }
1770
1771 void close() {
1772 if (fPtCount > 2) {
1773 this->addVec(fFirstVec);
1774 }
1775 }
1776
1777private:
1778 void addVec(const SkVector& vec) {
1779 SkASSERT(vec.fX || vec.fY);
1780 fVec0 = fVec1;
1781 fVec1 = vec;
1782 int sign = CrossProductSign(fVec0, fVec1);
1783 if (0 == fSign) {
1784 fSign = sign;
1785 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001786 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001787 fConvexity = SkPath::kConcave_Convexity;
1788 }
1789 }
1790 }
1791
1792 SkPoint fCurrPt;
1793 SkVector fVec0, fVec1, fFirstVec;
1794 int fPtCount; // non-degenerate points
1795 int fSign;
1796 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001797 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001798};
1799
1800SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1801 SkPoint pts[4];
1802 SkPath::Verb verb;
1803 SkPath::Iter iter(path, true);
1804
1805 int contourCount = 0;
1806 int count;
1807 Convexicator state;
1808
1809 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1810 switch (verb) {
1811 case kMove_Verb:
1812 if (++contourCount > 1) {
1813 return kConcave_Convexity;
1814 }
1815 pts[1] = pts[0];
1816 count = 1;
1817 break;
1818 case kLine_Verb: count = 1; break;
1819 case kQuad_Verb: count = 2; break;
1820 case kCubic_Verb: count = 3; break;
1821 case kClose_Verb:
1822 state.close();
1823 count = 0;
1824 break;
1825 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001826 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001827 return kConcave_Convexity;
1828 }
1829
1830 for (int i = 1; i <= count; i++) {
1831 state.addPt(pts[i]);
1832 }
1833 // early exit
1834 if (kConcave_Convexity == state.getConvexity()) {
1835 return kConcave_Convexity;
1836 }
1837 }
1838 return state.getConvexity();
1839}
reed@google.com69a99432012-01-10 18:00:10 +00001840
1841///////////////////////////////////////////////////////////////////////////////
1842
1843class ContourIter {
1844public:
1845 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1846
1847 bool done() const { return fDone; }
1848 // if !done() then these may be called
1849 int count() const { return fCurrPtCount; }
1850 const SkPoint* pts() const { return fCurrPt; }
1851 void next();
1852
1853private:
1854 int fCurrPtCount;
1855 const SkPoint* fCurrPt;
1856 const uint8_t* fCurrVerb;
1857 const uint8_t* fStopVerbs;
1858 bool fDone;
1859};
1860
1861ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
1862 const SkTDArray<SkPoint>& pts) {
1863 fStopVerbs = verbs.begin() + verbs.count();
1864
1865 fDone = false;
1866 fCurrPt = pts.begin();
1867 fCurrVerb = verbs.begin();
1868 fCurrPtCount = 0;
1869 this->next();
1870}
1871
1872void ContourIter::next() {
1873 if (fCurrVerb >= fStopVerbs) {
1874 fDone = true;
1875 }
1876 if (fDone) {
1877 return;
1878 }
1879
1880 // skip pts of prev contour
1881 fCurrPt += fCurrPtCount;
1882
1883 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
1884 int ptCount = 1; // moveTo
1885 const uint8_t* verbs = fCurrVerb;
1886
1887 for (++verbs; verbs < fStopVerbs; ++verbs) {
1888 switch (*verbs) {
1889 case SkPath::kMove_Verb:
1890 if (ptCount > 1) {
1891 // back up to revisit this Move next time arround, unless
1892 // the prev verb was also Move, which we know if ptCount==1
1893 verbs -= 1;
1894 }
1895 goto CONTOUR_END;
1896 case SkPath::kLine_Verb:
1897 ptCount += 1;
1898 break;
1899 case SkPath::kQuad_Verb:
1900 ptCount += 2;
1901 break;
1902 case SkPath::kCubic_Verb:
1903 ptCount += 3;
1904 break;
1905 default: // kClose_Verb, just keep going
1906 break;
1907 }
1908 }
1909CONTOUR_END:
1910 fCurrPtCount = ptCount;
1911 fCurrVerb = verbs;
1912}
1913
1914static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1915 return SkPoint::CrossProduct(p1 - p0, p2 - p0);
1916}
1917
1918static int find_max_y(const SkPoint pts[], int count) {
1919 SkASSERT(count > 0);
1920 SkScalar max = pts[0].fY;
1921 int maxIndex = 0;
1922 for (int i = 1; i < count; ++i) {
1923 if (pts[i].fY > max) {
1924 max = pts[i].fY;
1925 maxIndex = i;
1926 }
1927 }
1928 return maxIndex;
1929}
1930
1931bool SkPath::cheapComputeDirection(Direction* dir) const {
1932 // don't want to pay the cost for computing this if it
1933 // is unknown, so we don't call isConvex()
1934 const Convexity conv = this->getConvexityOrUnknown();
1935
1936 ContourIter iter(fVerbs, fPts);
1937
1938 for (; !iter.done(); iter.next()) {
1939 int n = iter.count();
1940 const SkPoint* pts = iter.pts();
1941
1942 SkScalar cross = 0;
1943 if (kConvex_Convexity == conv) {
1944 for (int i = 0; i < n - 2; ++i) {
1945 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
1946 if (cross) {
1947 break;
1948 }
1949 }
1950 } else {
1951 int i = find_max_y(pts, n);
1952 // loop around until we get a non-zero cross
1953 for (int j = 0; j < n; ++j) {
1954 if (i < n - 2) {
1955 cross = cross_prod(pts[i], pts[i + 1], pts[i + 2]);
1956 } else {
1957 cross = cross_prod(pts[i], pts[(i + 1) % n], pts[(i + 2) % n]);
1958 }
1959 if (cross) {
1960 break;
1961 }
1962 SkASSERT(i < n);
1963 if (++i == n) {
1964 i = 0;
1965 }
1966 }
1967 }
1968 if (cross) {
1969 if (dir) {
1970 *dir = cross > 0 ? kCW_Direction : kCCW_Direction;
1971 }
1972 return true;
1973 }
1974 }
1975 return false; // unknown
1976}
1977