blob: 22860bf616b0381d662356007e0162e842b2d5c7 [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
938 Iter iter(path, false);
939 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
1055///////////////////////////////////////////////////////////////////////////////
1056
1057void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1058 SkMatrix matrix;
1059
1060 matrix.setTranslate(dx, dy);
1061 this->transform(matrix, dst);
1062}
1063
1064#include "SkGeometry.h"
1065
1066static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1067 int level = 2) {
1068 if (--level >= 0) {
1069 SkPoint tmp[5];
1070
1071 SkChopQuadAtHalf(pts, tmp);
1072 subdivide_quad_to(path, &tmp[0], level);
1073 subdivide_quad_to(path, &tmp[2], level);
1074 } else {
1075 path->quadTo(pts[1], pts[2]);
1076 }
1077}
1078
1079static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1080 int level = 2) {
1081 if (--level >= 0) {
1082 SkPoint tmp[7];
1083
1084 SkChopCubicAtHalf(pts, tmp);
1085 subdivide_cubic_to(path, &tmp[0], level);
1086 subdivide_cubic_to(path, &tmp[3], level);
1087 } else {
1088 path->cubicTo(pts[1], pts[2], pts[3]);
1089 }
1090}
1091
1092void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1093 SkDEBUGCODE(this->validate();)
1094 if (dst == NULL) {
1095 dst = (SkPath*)this;
1096 }
1097
tomhudson@google.com8d430182011-06-06 19:11:19 +00001098 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 SkPath tmp;
1100 tmp.fFillType = fFillType;
1101
1102 SkPath::Iter iter(*this, false);
1103 SkPoint pts[4];
1104 SkPath::Verb verb;
1105
1106 while ((verb = iter.next(pts)) != kDone_Verb) {
1107 switch (verb) {
1108 case kMove_Verb:
1109 tmp.moveTo(pts[0]);
1110 break;
1111 case kLine_Verb:
1112 tmp.lineTo(pts[1]);
1113 break;
1114 case kQuad_Verb:
1115 subdivide_quad_to(&tmp, pts);
1116 break;
1117 case kCubic_Verb:
1118 subdivide_cubic_to(&tmp, pts);
1119 break;
1120 case kClose_Verb:
1121 tmp.close();
1122 break;
1123 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001124 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 break;
1126 }
1127 }
1128
1129 dst->swap(tmp);
1130 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1131 } else {
1132 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001133 // fBoundsIsDirty before we set it
1134 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001136 matrix.mapRect(&dst->fBounds, fBounds);
1137 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001138 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001139 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001140 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141 }
1142
1143 if (this != dst) {
1144 dst->fVerbs = fVerbs;
1145 dst->fPts.setCount(fPts.count());
1146 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001147 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001148 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149 }
1150 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1151 SkDEBUGCODE(dst->validate();)
1152 }
1153}
1154
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155///////////////////////////////////////////////////////////////////////////////
1156///////////////////////////////////////////////////////////////////////////////
1157
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001158enum SegmentState {
1159 kAfterClose_SegmentState, // We will need a move next, but we have a
1160 // previous close pt to use for the new move.
1161 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1162 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1163 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001164};
1165
1166SkPath::Iter::Iter() {
1167#ifdef SK_DEBUG
1168 fPts = NULL;
1169 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001170 fForceClose = fCloseLine = false;
1171 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172#endif
1173 // need to init enough to make next() harmlessly return kDone_Verb
1174 fVerbs = NULL;
1175 fVerbStop = NULL;
1176 fNeedClose = false;
1177}
1178
1179SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1180 this->setPath(path, forceClose);
1181}
1182
1183void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1184 fPts = path.fPts.begin();
1185 fVerbs = path.fVerbs.begin();
1186 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001187 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001188 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 fForceClose = SkToU8(forceClose);
1190 fNeedClose = false;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001191 fSegmentState = kAfterClose_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192}
1193
1194bool SkPath::Iter::isClosedContour() const {
1195 if (fVerbs == NULL || fVerbs == fVerbStop) {
1196 return false;
1197 }
1198 if (fForceClose) {
1199 return true;
1200 }
1201
1202 const uint8_t* verbs = fVerbs;
1203 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001204
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 if (kMove_Verb == *verbs) {
1206 verbs += 1; // skip the initial moveto
1207 }
1208
1209 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001210 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 if (kMove_Verb == v) {
1212 break;
1213 }
1214 if (kClose_Verb == v) {
1215 return true;
1216 }
1217 }
1218 return false;
1219}
1220
1221SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1222 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001223 // A special case: if both points are NaN, SkPoint::operation== returns
1224 // false, but the iterator expects that they are treated as the same.
1225 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001226 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1227 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001228 return kClose_Verb;
1229 }
1230
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 if (pts) {
1232 pts[0] = fLastPt;
1233 pts[1] = fMoveTo;
1234 }
1235 fLastPt = fMoveTo;
1236 fCloseLine = true;
1237 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001238 } else {
1239 pts[0] = fMoveTo;
1240 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242}
1243
1244bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001245 if (fSegmentState == kAfterClose_SegmentState) {
1246 // We have closed a curve and have a primitive, so we need a move.
1247 // Set the first return pt to the most recent move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 if (pts) {
1249 *pts = fMoveTo;
1250 }
1251 fNeedClose = fForceClose;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001252 fSegmentState = kAfterMove_SegmentState;
1253 fVerbs -= 1; // Step back to see the primitive again
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254 return true;
1255 }
1256
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001257 if (fSegmentState == kAfterMove_SegmentState) {
1258 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 if (pts) {
1260 *pts = fMoveTo;
1261 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001262 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001264 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1265 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266 if (pts) {
1267 *pts = fPts[-1];
1268 }
1269 }
1270 return false;
1271}
1272
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001273void SkPath::Iter::consumeDegenerateSegments() {
1274 // We need to step over anything that will not move the current draw point
1275 // forward before the next move is seen
1276 const uint8_t* lastMoveVerb = 0;
1277 const SkPoint* lastMovePt = 0;
1278 SkPoint lastPt = fLastPt;
1279 while (fVerbs != fVerbStop) {
1280 unsigned verb = *fVerbs;
1281 switch (verb) {
1282 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001283 // Keep a record of this most recent move
1284 lastMoveVerb = fVerbs;
1285 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001286 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001287 fVerbs++;
1288 fPts++;
1289 break;
1290
1291 case kClose_Verb:
1292 // A close when we are in a segment is always valid
1293 if (fSegmentState == kAfterPrimitive_SegmentState) {
1294 return;
1295 }
1296 // A close at any other time must be ignored
1297 fVerbs++;
1298 break;
1299
1300 case kLine_Verb:
1301 if (!IsLineDegenerate(lastPt, fPts[0])) {
1302 if (lastMoveVerb) {
1303 fVerbs = lastMoveVerb;
1304 fPts = lastMovePt;
1305 return;
1306 }
1307 return;
1308 }
1309 // Ignore this line and continue
1310 fVerbs++;
1311 fPts++;
1312 break;
1313
1314 case kQuad_Verb:
1315 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1316 if (lastMoveVerb) {
1317 fVerbs = lastMoveVerb;
1318 fPts = lastMovePt;
1319 return;
1320 }
1321 return;
1322 }
1323 // Ignore this line and continue
1324 fVerbs++;
1325 fPts += 2;
1326 break;
1327
1328 case kCubic_Verb:
1329 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1330 if (lastMoveVerb) {
1331 fVerbs = lastMoveVerb;
1332 fPts = lastMovePt;
1333 return;
1334 }
1335 return;
1336 }
1337 // Ignore this line and continue
1338 fVerbs++;
1339 fPts += 3;
1340 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001341
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001342 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001343 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001344 }
1345 }
1346}
1347
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001349#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001350 this->consumeDegenerateSegments();
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001351#endif
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001352
reed@android.com8a1c16f2008-12-17 15:59:43 +00001353 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001354 // Close the curve if requested and if there is some curve to close
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001355#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1356 if (fNeedClose) {
1357#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001358 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001359#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 if (kLine_Verb == this->autoClose(pts)) {
1361 return kLine_Verb;
1362 }
1363 fNeedClose = false;
1364 return kClose_Verb;
1365 }
1366 return kDone_Verb;
1367 }
1368
1369 unsigned verb = *fVerbs++;
1370 const SkPoint* srcPts = fPts;
1371
1372 switch (verb) {
1373 case kMove_Verb:
1374 if (fNeedClose) {
1375 fVerbs -= 1;
1376 verb = this->autoClose(pts);
1377 if (verb == kClose_Verb) {
1378 fNeedClose = false;
1379 }
1380 return (Verb)verb;
1381 }
1382 if (fVerbs == fVerbStop) { // might be a trailing moveto
1383 return kDone_Verb;
1384 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001385 fMoveTo = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 if (pts) {
1387 pts[0] = *srcPts;
1388 }
1389 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001390 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001391#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001392 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001393#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 fNeedClose = fForceClose;
1395 break;
1396 case kLine_Verb:
1397 if (this->cons_moveTo(pts)) {
1398 return kMove_Verb;
1399 }
1400 if (pts) {
1401 pts[1] = srcPts[0];
1402 }
1403 fLastPt = srcPts[0];
1404 fCloseLine = false;
1405 srcPts += 1;
1406 break;
1407 case kQuad_Verb:
1408 if (this->cons_moveTo(pts)) {
1409 return kMove_Verb;
1410 }
1411 if (pts) {
1412 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1413 }
1414 fLastPt = srcPts[1];
1415 srcPts += 2;
1416 break;
1417 case kCubic_Verb:
1418 if (this->cons_moveTo(pts)) {
1419 return kMove_Verb;
1420 }
1421 if (pts) {
1422 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1423 }
1424 fLastPt = srcPts[2];
1425 srcPts += 3;
1426 break;
1427 case kClose_Verb:
1428 verb = this->autoClose(pts);
1429 if (verb == kLine_Verb) {
1430 fVerbs -= 1;
1431 } else {
1432 fNeedClose = false;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001433#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001434 fSegmentState = kAfterClose_SegmentState;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001435#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001437#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1438 fSegmentState = kAfterClose_SegmentState;
1439#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001440 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001441#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 break;
1443 }
1444 fPts = srcPts;
1445 return (Verb)verb;
1446}
1447
1448///////////////////////////////////////////////////////////////////////////////
1449
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450/*
1451 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1452*/
1453
reed@google.com73945652011-04-25 19:04:27 +00001454void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 SkDEBUGCODE(this->validate();)
1456
1457 buffer.write32(fPts.count());
1458 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001459 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1461 buffer.writePad(fVerbs.begin(), fVerbs.count());
1462}
1463
reed@google.com73945652011-04-25 19:04:27 +00001464void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 fPts.setCount(buffer.readS32());
1466 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001467 uint32_t packed = buffer.readS32();
1468 fFillType = packed >> 8;
1469 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1471 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001472
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001473 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001474 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475
1476 SkDEBUGCODE(this->validate();)
1477}
1478
1479///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480///////////////////////////////////////////////////////////////////////////////
1481
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482void SkPath::dump(bool forceClose, const char title[]) const {
1483 Iter iter(*this, forceClose);
1484 SkPoint pts[4];
1485 Verb verb;
1486
1487 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1488 title ? title : "");
1489
1490 while ((verb = iter.next(pts)) != kDone_Verb) {
1491 switch (verb) {
1492 case kMove_Verb:
1493#ifdef SK_CAN_USE_FLOAT
1494 SkDebugf(" path: moveTo [%g %g]\n",
1495 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1496#else
1497 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1498#endif
1499 break;
1500 case kLine_Verb:
1501#ifdef SK_CAN_USE_FLOAT
1502 SkDebugf(" path: lineTo [%g %g]\n",
1503 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1504#else
1505 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1506#endif
1507 break;
1508 case kQuad_Verb:
1509#ifdef SK_CAN_USE_FLOAT
1510 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1511 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1512 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1513#else
1514 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1515 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1516#endif
1517 break;
1518 case kCubic_Verb:
1519#ifdef SK_CAN_USE_FLOAT
1520 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1521 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1522 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1523 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1524#else
1525 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1526 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1527 pts[3].fX, pts[3].fY);
1528#endif
1529 break;
1530 case kClose_Verb:
1531 SkDebugf(" path: close\n");
1532 break;
1533 default:
1534 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1535 verb = kDone_Verb; // stop the loop
1536 break;
1537 }
1538 }
1539 SkDebugf("path: done %s\n", title ? title : "");
1540}
1541
reed@android.come522ca52009-11-23 20:10:41 +00001542void SkPath::dump() const {
1543 this->dump(false);
1544}
1545
1546#ifdef SK_DEBUG
1547void SkPath::validate() const {
1548 SkASSERT(this != NULL);
1549 SkASSERT((fFillType & ~3) == 0);
1550 fPts.validate();
1551 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001552
reed@android.come522ca52009-11-23 20:10:41 +00001553 if (!fBoundsIsDirty) {
1554 SkRect bounds;
1555 compute_pt_bounds(&bounds, fPts);
1556 if (fPts.count() <= 1) {
1557 // if we're empty, fBounds may be empty but translated, so we can't
1558 // necessarily compare to bounds directly
1559 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1560 // be [2, 2, 2, 2]
1561 SkASSERT(bounds.isEmpty());
1562 SkASSERT(fBounds.isEmpty());
1563 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001564 if (bounds.isEmpty()) {
1565 SkASSERT(fBounds.isEmpty());
1566 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001567 if (!fBounds.isEmpty()) {
1568 SkASSERT(fBounds.contains(bounds));
1569 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001570 }
reed@android.come522ca52009-11-23 20:10:41 +00001571 }
1572 }
reed@google.com10296cc2011-09-21 12:29:05 +00001573
1574 uint32_t mask = 0;
1575 for (int i = 0; i < fVerbs.count(); i++) {
1576 switch (fVerbs[i]) {
1577 case kLine_Verb:
1578 mask |= kLine_SegmentMask;
1579 break;
1580 case kQuad_Verb:
1581 mask |= kQuad_SegmentMask;
1582 break;
1583 case kCubic_Verb:
1584 mask |= kCubic_SegmentMask;
1585 }
1586 }
1587 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001588}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589#endif
reed@android.come522ca52009-11-23 20:10:41 +00001590
reed@google.com04863fa2011-05-15 04:08:24 +00001591///////////////////////////////////////////////////////////////////////////////
1592
reed@google.com0b7b9822011-05-16 12:29:27 +00001593static int sign(SkScalar x) { return x < 0; }
1594#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001595
reed@google.com04863fa2011-05-15 04:08:24 +00001596static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001597 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001598}
1599
1600// only valid for a single contour
1601struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001602 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001603 fSign = 0;
1604 // warnings
1605 fCurrPt.set(0, 0);
1606 fVec0.set(0, 0);
1607 fVec1.set(0, 0);
1608 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001609
1610 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001611 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001612 }
1613
1614 SkPath::Convexity getConvexity() const { return fConvexity; }
1615
1616 void addPt(const SkPoint& pt) {
1617 if (SkPath::kConcave_Convexity == fConvexity) {
1618 return;
1619 }
1620
1621 if (0 == fPtCount) {
1622 fCurrPt = pt;
1623 ++fPtCount;
1624 } else {
1625 SkVector vec = pt - fCurrPt;
1626 if (vec.fX || vec.fY) {
1627 fCurrPt = pt;
1628 if (++fPtCount == 2) {
1629 fFirstVec = fVec1 = vec;
1630 } else {
1631 SkASSERT(fPtCount > 2);
1632 this->addVec(vec);
1633 }
reed@google.com85b6e392011-05-15 20:25:17 +00001634
1635 int sx = sign(vec.fX);
1636 int sy = sign(vec.fY);
1637 fDx += (sx != fSx);
1638 fDy += (sy != fSy);
1639 fSx = sx;
1640 fSy = sy;
1641
1642 if (fDx > 3 || fDy > 3) {
1643 fConvexity = SkPath::kConcave_Convexity;
1644 }
reed@google.com04863fa2011-05-15 04:08:24 +00001645 }
1646 }
1647 }
1648
1649 void close() {
1650 if (fPtCount > 2) {
1651 this->addVec(fFirstVec);
1652 }
1653 }
1654
1655private:
1656 void addVec(const SkVector& vec) {
1657 SkASSERT(vec.fX || vec.fY);
1658 fVec0 = fVec1;
1659 fVec1 = vec;
1660 int sign = CrossProductSign(fVec0, fVec1);
1661 if (0 == fSign) {
1662 fSign = sign;
1663 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001664 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001665 fConvexity = SkPath::kConcave_Convexity;
1666 }
1667 }
1668 }
1669
1670 SkPoint fCurrPt;
1671 SkVector fVec0, fVec1, fFirstVec;
1672 int fPtCount; // non-degenerate points
1673 int fSign;
1674 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001675 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001676};
1677
1678SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1679 SkPoint pts[4];
1680 SkPath::Verb verb;
1681 SkPath::Iter iter(path, true);
1682
1683 int contourCount = 0;
1684 int count;
1685 Convexicator state;
1686
1687 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1688 switch (verb) {
1689 case kMove_Verb:
1690 if (++contourCount > 1) {
1691 return kConcave_Convexity;
1692 }
1693 pts[1] = pts[0];
1694 count = 1;
1695 break;
1696 case kLine_Verb: count = 1; break;
1697 case kQuad_Verb: count = 2; break;
1698 case kCubic_Verb: count = 3; break;
1699 case kClose_Verb:
1700 state.close();
1701 count = 0;
1702 break;
1703 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001704 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001705 return kConcave_Convexity;
1706 }
1707
1708 for (int i = 1; i <= count; i++) {
1709 state.addPt(pts[i]);
1710 }
1711 // early exit
1712 if (kConcave_Convexity == state.getConvexity()) {
1713 return kConcave_Convexity;
1714 }
1715 }
1716 return state.getConvexity();
1717}