blob: 07e54762da3e0751baf00df8add6521aa32dbefb [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();)
202
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000203 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204}
205
caryclark@google.comf1316942011-07-26 19:54:45 +0000206/*
207 Determines if path is a rect by keeping track of changes in direction
208 and looking for a loop either clockwise or counterclockwise.
209
210 The direction is computed such that:
211 0: vertical up
212 1: horizontal right
213 2: vertical down
214 3: horizontal left
215
216A rectangle cycles up/right/down/left or up/left/down/right.
217
218The test fails if:
219 The path is closed, and followed by a line.
220 A second move creates a new endpoint.
221 A diagonal line is parsed.
222 There's more than four changes of direction.
223 There's a discontinuity on the line (e.g., a move in the middle)
224 The line reverses direction.
225 The rectangle doesn't complete a cycle.
226 The path contains a quadratic or cubic.
227 The path contains fewer than four points.
228 The final point isn't equal to the first point.
229
230It's OK if the path has:
231 Several colinear line segments composing a rectangle side.
232 Single points on the rectangle side.
233
234The direction takes advantage of the corners found since opposite sides
235must travel in opposite directions.
236
237FIXME: Allow colinear quads and cubics to be treated like lines.
238FIXME: If the API passes fill-only, return true if the filled stroke
239 is a rectangle, though the caller failed to close the path.
240 */
241bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000243
caryclark@google.comf1316942011-07-26 19:54:45 +0000244 int corners = 0;
245 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000246 first.set(0, 0);
247 last.set(0, 0);
248 int firstDirection = 0;
249 int lastDirection = 0;
250 int nextDirection = 0;
251 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000252 bool autoClose = false;
253 const uint8_t* verbs = fVerbs.begin();
254 const uint8_t* verbStop = fVerbs.end();
255 const SkPoint* pts = fPts.begin();
256 while (verbs != verbStop) {
257 switch (*verbs++) {
258 case kClose_Verb:
259 pts = fPts.begin();
260 autoClose = true;
261 case kLine_Verb: {
262 SkScalar left = last.fX;
263 SkScalar top = last.fY;
264 SkScalar right = pts->fX;
265 SkScalar bottom = pts->fY;
266 ++pts;
267 if (left != right && top != bottom) {
268 return false; // diagonal
269 }
270 if (left == right && top == bottom) {
271 break; // single point on side OK
272 }
273 nextDirection = (left != right) << 0 |
274 (left < right || top < bottom) << 1;
275 if (0 == corners) {
276 firstDirection = nextDirection;
277 first = last;
278 last = pts[-1];
279 corners = 1;
280 closedOrMoved = false;
281 break;
282 }
283 if (closedOrMoved) {
284 return false; // closed followed by a line
285 }
286 closedOrMoved = autoClose;
287 if (lastDirection != nextDirection) {
288 if (++corners > 4) {
289 return false; // too many direction changes
290 }
291 }
292 last = pts[-1];
293 if (lastDirection == nextDirection) {
294 break; // colinear segment
295 }
296 // Possible values for corners are 2, 3, and 4.
297 // When corners == 3, nextDirection opposes firstDirection.
298 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000299 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000300 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
301 if ((directionCycle ^ turn) != nextDirection) {
302 return false; // direction didn't follow cycle
303 }
304 break;
305 }
306 case kQuad_Verb:
307 case kCubic_Verb:
308 return false; // quadratic, cubic not allowed
309 case kMove_Verb:
310 last = *pts++;
311 closedOrMoved = true;
312 break;
313 }
314 lastDirection = nextDirection;
315 }
316 // Success if 4 corners and first point equals last
317 bool result = 4 == corners && first == last;
318 if (result && rect) {
319 *rect = getBounds();
320 }
321 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322}
323
324int SkPath::getPoints(SkPoint copy[], int max) const {
325 SkDEBUGCODE(this->validate();)
326
327 SkASSERT(max >= 0);
328 int count = fPts.count();
329 if (copy && max > 0 && count > 0) {
330 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
331 }
332 return count;
333}
334
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000335SkPoint SkPath::getPoint(int index) const {
336 if ((unsigned)index < (unsigned)fPts.count()) {
337 return fPts[index];
338 }
339 return SkPoint::Make(0, 0);
340}
341
reed@google.com294dd7b2011-10-11 11:58:32 +0000342bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343 SkDEBUGCODE(this->validate();)
344
reed@google.com294dd7b2011-10-11 11:58:32 +0000345 int count = fPts.count();
346 if (count > 0) {
347 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348 *lastPt = fPts[count - 1];
349 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000350 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000352 if (lastPt) {
353 lastPt->set(0, 0);
354 }
355 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356}
357
358void SkPath::setLastPt(SkScalar x, SkScalar y) {
359 SkDEBUGCODE(this->validate();)
360
361 int count = fPts.count();
362 if (count == 0) {
363 this->moveTo(x, y);
364 } else {
365 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000366 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 }
368}
369
reed@android.comd252db02009-04-01 18:31:44 +0000370void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000372 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373
reed@android.comd252db02009-04-01 18:31:44 +0000374 fBoundsIsDirty = false;
375 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376}
377
reed@google.com04863fa2011-05-15 04:08:24 +0000378void SkPath::setConvexity(Convexity c) {
379 if (fConvexity != c) {
380 fConvexity = c;
381 GEN_ID_INC;
382 }
383}
384
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385//////////////////////////////////////////////////////////////////////////////
386// Construction methods
387
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000388#define DIRTY_AFTER_EDIT \
389 do { \
390 fBoundsIsDirty = true; \
391 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000392 } while (0)
393
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394void SkPath::incReserve(U16CPU inc) {
395 SkDEBUGCODE(this->validate();)
396
397 fVerbs.setReserve(fVerbs.count() + inc);
398 fPts.setReserve(fPts.count() + inc);
399
400 SkDEBUGCODE(this->validate();)
401}
402
403void SkPath::moveTo(SkScalar x, SkScalar y) {
404 SkDEBUGCODE(this->validate();)
405
406 int vc = fVerbs.count();
407 SkPoint* pt;
408
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000409 pt = fPts.append();
410 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 pt->set(x, y);
412
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000413 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000414 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415}
416
417void SkPath::rMoveTo(SkScalar x, SkScalar y) {
418 SkPoint pt;
419 this->getLastPt(&pt);
420 this->moveTo(pt.fX + x, pt.fY + y);
421}
422
423void SkPath::lineTo(SkScalar x, SkScalar y) {
424 SkDEBUGCODE(this->validate();)
425
426 if (fVerbs.count() == 0) {
427 fPts.append()->set(0, 0);
428 *fVerbs.append() = kMove_Verb;
429 }
430 fPts.append()->set(x, y);
431 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000432 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000434 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000435 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436}
437
438void SkPath::rLineTo(SkScalar x, SkScalar y) {
439 SkPoint pt;
440 this->getLastPt(&pt);
441 this->lineTo(pt.fX + x, pt.fY + y);
442}
443
444void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
445 SkDEBUGCODE(this->validate();)
446
447 if (fVerbs.count() == 0) {
448 fPts.append()->set(0, 0);
449 *fVerbs.append() = kMove_Verb;
450 }
451
452 SkPoint* pts = fPts.append(2);
453 pts[0].set(x1, y1);
454 pts[1].set(x2, y2);
455 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000456 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000458 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000459 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000460}
461
462void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
463 SkPoint pt;
464 this->getLastPt(&pt);
465 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
466}
467
468void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
469 SkScalar x3, SkScalar y3) {
470 SkDEBUGCODE(this->validate();)
471
472 if (fVerbs.count() == 0) {
473 fPts.append()->set(0, 0);
474 *fVerbs.append() = kMove_Verb;
475 }
476 SkPoint* pts = fPts.append(3);
477 pts[0].set(x1, y1);
478 pts[1].set(x2, y2);
479 pts[2].set(x3, y3);
480 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000481 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000482
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000483 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000484 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485}
486
487void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
488 SkScalar x3, SkScalar y3) {
489 SkPoint pt;
490 this->getLastPt(&pt);
491 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
492 pt.fX + x3, pt.fY + y3);
493}
494
495void SkPath::close() {
496 SkDEBUGCODE(this->validate();)
497
498 int count = fVerbs.count();
499 if (count > 0) {
500 switch (fVerbs[count - 1]) {
501 case kLine_Verb:
502 case kQuad_Verb:
503 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000504 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000505 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000506 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 break;
508 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000509 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000510 break;
511 }
512 }
513}
514
515///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000516
reed@android.com8a1c16f2008-12-17 15:59:43 +0000517void SkPath::addRect(const SkRect& rect, Direction dir) {
518 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
519}
520
521void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
522 SkScalar bottom, Direction dir) {
523 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
524
525 this->incReserve(5);
526
527 this->moveTo(left, top);
528 if (dir == kCCW_Direction) {
529 this->lineTo(left, bottom);
530 this->lineTo(right, bottom);
531 this->lineTo(right, top);
532 } else {
533 this->lineTo(right, top);
534 this->lineTo(right, bottom);
535 this->lineTo(left, bottom);
536 }
537 this->close();
538}
539
540#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
541
542void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
543 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 SkScalar w = rect.width();
545 SkScalar halfW = SkScalarHalf(w);
546 SkScalar h = rect.height();
547 SkScalar halfH = SkScalarHalf(h);
548
549 if (halfW <= 0 || halfH <= 0) {
550 return;
551 }
552
reed@google.comabf15c12011-01-18 20:35:51 +0000553 bool skip_hori = rx >= halfW;
554 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555
556 if (skip_hori && skip_vert) {
557 this->addOval(rect, dir);
558 return;
559 }
reed@google.comabf15c12011-01-18 20:35:51 +0000560
561 SkAutoPathBoundsUpdate apbu(this, rect);
562
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563 if (skip_hori) {
564 rx = halfW;
565 } else if (skip_vert) {
566 ry = halfH;
567 }
568
569 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
570 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
571
572 this->incReserve(17);
573 this->moveTo(rect.fRight - rx, rect.fTop);
574 if (dir == kCCW_Direction) {
575 if (!skip_hori) {
576 this->lineTo(rect.fLeft + rx, rect.fTop); // top
577 }
578 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
579 rect.fLeft, rect.fTop + ry - sy,
580 rect.fLeft, rect.fTop + ry); // top-left
581 if (!skip_vert) {
582 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
583 }
584 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
585 rect.fLeft + rx - sx, rect.fBottom,
586 rect.fLeft + rx, rect.fBottom); // bot-left
587 if (!skip_hori) {
588 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
589 }
590 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
591 rect.fRight, rect.fBottom - ry + sy,
592 rect.fRight, rect.fBottom - ry); // bot-right
593 if (!skip_vert) {
594 this->lineTo(rect.fRight, rect.fTop + ry);
595 }
596 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
597 rect.fRight - rx + sx, rect.fTop,
598 rect.fRight - rx, rect.fTop); // top-right
599 } else {
600 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
601 rect.fRight, rect.fTop + ry - sy,
602 rect.fRight, rect.fTop + ry); // top-right
603 if (!skip_vert) {
604 this->lineTo(rect.fRight, rect.fBottom - ry);
605 }
606 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
607 rect.fRight - rx + sx, rect.fBottom,
608 rect.fRight - rx, rect.fBottom); // bot-right
609 if (!skip_hori) {
610 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
611 }
612 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
613 rect.fLeft, rect.fBottom - ry + sy,
614 rect.fLeft, rect.fBottom - ry); // bot-left
615 if (!skip_vert) {
616 this->lineTo(rect.fLeft, rect.fTop + ry); // left
617 }
618 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
619 rect.fLeft + rx - sx, rect.fTop,
620 rect.fLeft + rx, rect.fTop); // top-left
621 if (!skip_hori) {
622 this->lineTo(rect.fRight - rx, rect.fTop); // top
623 }
624 }
625 this->close();
626}
627
628static void add_corner_arc(SkPath* path, const SkRect& rect,
629 SkScalar rx, SkScalar ry, int startAngle,
630 SkPath::Direction dir, bool forceMoveTo) {
631 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
632 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000633
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 SkRect r;
635 r.set(-rx, -ry, rx, ry);
636
637 switch (startAngle) {
638 case 0:
639 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
640 break;
641 case 90:
642 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
643 break;
644 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
645 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
646 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
647 }
reed@google.comabf15c12011-01-18 20:35:51 +0000648
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 SkScalar start = SkIntToScalar(startAngle);
650 SkScalar sweep = SkIntToScalar(90);
651 if (SkPath::kCCW_Direction == dir) {
652 start += sweep;
653 sweep = -sweep;
654 }
reed@google.comabf15c12011-01-18 20:35:51 +0000655
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 path->arcTo(r, start, sweep, forceMoveTo);
657}
658
659void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
660 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000661 // abort before we invoke SkAutoPathBoundsUpdate()
662 if (rect.isEmpty()) {
663 return;
664 }
665
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 SkAutoPathBoundsUpdate apbu(this, rect);
667
668 if (kCW_Direction == dir) {
669 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
670 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
671 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
672 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
673 } else {
674 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
675 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
676 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
677 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
678 }
679 this->close();
680}
681
682void SkPath::addOval(const SkRect& oval, Direction dir) {
683 SkAutoPathBoundsUpdate apbu(this, oval);
684
685 SkScalar cx = oval.centerX();
686 SkScalar cy = oval.centerY();
687 SkScalar rx = SkScalarHalf(oval.width());
688 SkScalar ry = SkScalarHalf(oval.height());
689#if 0 // these seem faster than using quads (1/2 the number of edges)
690 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
691 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
692
693 this->incReserve(13);
694 this->moveTo(cx + rx, cy);
695 if (dir == kCCW_Direction) {
696 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
697 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
698 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
699 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
700 } else {
701 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
702 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
703 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
704 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
705 }
706#else
707 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
708 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
709 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
710 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
711
712 /*
713 To handle imprecision in computing the center and radii, we revert to
714 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
715 to ensure that we don't exceed the oval's bounds *ever*, since we want
716 to use oval for our fast-bounds, rather than have to recompute it.
717 */
718 const SkScalar L = oval.fLeft; // cx - rx
719 const SkScalar T = oval.fTop; // cy - ry
720 const SkScalar R = oval.fRight; // cx + rx
721 const SkScalar B = oval.fBottom; // cy + ry
722
723 this->incReserve(17); // 8 quads + close
724 this->moveTo(R, cy);
725 if (dir == kCCW_Direction) {
726 this->quadTo( R, cy - sy, cx + mx, cy - my);
727 this->quadTo(cx + sx, T, cx , T);
728 this->quadTo(cx - sx, T, cx - mx, cy - my);
729 this->quadTo( L, cy - sy, L, cy );
730 this->quadTo( L, cy + sy, cx - mx, cy + my);
731 this->quadTo(cx - sx, B, cx , B);
732 this->quadTo(cx + sx, B, cx + mx, cy + my);
733 this->quadTo( R, cy + sy, R, cy );
734 } else {
735 this->quadTo( R, cy + sy, cx + mx, cy + my);
736 this->quadTo(cx + sx, B, cx , B);
737 this->quadTo(cx - sx, B, cx - mx, cy + my);
738 this->quadTo( L, cy + sy, L, cy );
739 this->quadTo( L, cy - sy, cx - mx, cy - my);
740 this->quadTo(cx - sx, T, cx , T);
741 this->quadTo(cx + sx, T, cx + mx, cy - my);
742 this->quadTo( R, cy - sy, R, cy );
743 }
744#endif
745 this->close();
746}
747
748void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
749 if (r > 0) {
750 SkRect rect;
751 rect.set(x - r, y - r, x + r, y + r);
752 this->addOval(rect, dir);
753 }
754}
755
756#include "SkGeometry.h"
757
758static int build_arc_points(const SkRect& oval, SkScalar startAngle,
759 SkScalar sweepAngle,
760 SkPoint pts[kSkBuildQuadArcStorage]) {
761 SkVector start, stop;
762
763 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
764 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
765 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000766
767 /* If the sweep angle is nearly (but less than) 360, then due to precision
768 loss in radians-conversion and/or sin/cos, we may end up with coincident
769 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
770 of drawing a nearly complete circle (good).
771 e.g. canvas.drawArc(0, 359.99, ...)
772 -vs- canvas.drawArc(0, 359.9, ...)
773 We try to detect this edge case, and tweak the stop vector
774 */
775 if (start == stop) {
776 SkScalar sw = SkScalarAbs(sweepAngle);
777 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
778 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
779 // make a guess at a tiny angle (in radians) to tweak by
780 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
781 // not sure how much will be enough, so we use a loop
782 do {
783 stopRad -= deltaRad;
784 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
785 } while (start == stop);
786 }
787 }
788
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000790
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
792 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000793
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 return SkBuildQuadArc(start, stop,
795 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
796 &matrix, pts);
797}
798
799void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
800 bool forceMoveTo) {
801 if (oval.width() < 0 || oval.height() < 0) {
802 return;
803 }
804
805 SkPoint pts[kSkBuildQuadArcStorage];
806 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
807 SkASSERT((count & 1) == 1);
808
809 if (fVerbs.count() == 0) {
810 forceMoveTo = true;
811 }
812 this->incReserve(count);
813 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
814 for (int i = 1; i < count; i += 2) {
815 this->quadTo(pts[i], pts[i+1]);
816 }
817}
818
819void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
820 SkScalar sweepAngle) {
821 if (oval.isEmpty() || 0 == sweepAngle) {
822 return;
823 }
824
825 const SkScalar kFullCircleAngle = SkIntToScalar(360);
826
827 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
828 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
829 return;
830 }
831
832 SkPoint pts[kSkBuildQuadArcStorage];
833 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
834
835 this->incReserve(count);
836 this->moveTo(pts[0]);
837 for (int i = 1; i < count; i += 2) {
838 this->quadTo(pts[i], pts[i+1]);
839 }
840}
841
842/*
843 Need to handle the case when the angle is sharp, and our computed end-points
844 for the arc go behind pt1 and/or p2...
845*/
846void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
847 SkScalar radius) {
848 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000849
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 // need to know our prev pt so we can construct tangent vectors
851 {
852 SkPoint start;
853 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000854 // Handle degenerate cases by adding a line to the first point and
855 // bailing out.
856 if ((x1 == start.fX && y1 == start.fY) ||
857 (x1 == x2 && y1 == y2) ||
858 radius == 0) {
859 this->lineTo(x1, y1);
860 return;
861 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 before.setNormalize(x1 - start.fX, y1 - start.fY);
863 after.setNormalize(x2 - x1, y2 - y1);
864 }
reed@google.comabf15c12011-01-18 20:35:51 +0000865
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866 SkScalar cosh = SkPoint::DotProduct(before, after);
867 SkScalar sinh = SkPoint::CrossProduct(before, after);
868
869 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000870 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871 return;
872 }
reed@google.comabf15c12011-01-18 20:35:51 +0000873
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
875 if (dist < 0) {
876 dist = -dist;
877 }
878
879 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
880 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
881 SkRotationDirection arcDir;
882
883 // now turn before/after into normals
884 if (sinh > 0) {
885 before.rotateCCW();
886 after.rotateCCW();
887 arcDir = kCW_SkRotationDirection;
888 } else {
889 before.rotateCW();
890 after.rotateCW();
891 arcDir = kCCW_SkRotationDirection;
892 }
893
894 SkMatrix matrix;
895 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000896
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 matrix.setScale(radius, radius);
898 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
899 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000900
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000902
reed@android.com8a1c16f2008-12-17 15:59:43 +0000903 this->incReserve(count);
904 // [xx,yy] == pts[0]
905 this->lineTo(xx, yy);
906 for (int i = 1; i < count; i += 2) {
907 this->quadTo(pts[i], pts[i+1]);
908 }
909}
910
911///////////////////////////////////////////////////////////////////////////////
912
913void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
914 SkMatrix matrix;
915
916 matrix.setTranslate(dx, dy);
917 this->addPath(path, matrix);
918}
919
920void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
921 this->incReserve(path.fPts.count());
922
923 Iter iter(path, false);
924 SkPoint pts[4];
925 Verb verb;
926
927 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
928
929 while ((verb = iter.next(pts)) != kDone_Verb) {
930 switch (verb) {
931 case kMove_Verb:
932 proc(matrix, &pts[0], &pts[0], 1);
933 this->moveTo(pts[0]);
934 break;
935 case kLine_Verb:
936 proc(matrix, &pts[1], &pts[1], 1);
937 this->lineTo(pts[1]);
938 break;
939 case kQuad_Verb:
940 proc(matrix, &pts[1], &pts[1], 2);
941 this->quadTo(pts[1], pts[2]);
942 break;
943 case kCubic_Verb:
944 proc(matrix, &pts[1], &pts[1], 3);
945 this->cubicTo(pts[1], pts[2], pts[3]);
946 break;
947 case kClose_Verb:
948 this->close();
949 break;
950 default:
951 SkASSERT(!"unknown verb");
952 }
953 }
954}
955
956///////////////////////////////////////////////////////////////////////////////
957
958static const uint8_t gPtsInVerb[] = {
959 1, // kMove
960 1, // kLine
961 2, // kQuad
962 3, // kCubic
963 0, // kClose
964 0 // kDone
965};
966
967// ignore the initial moveto, and stop when the 1st contour ends
968void SkPath::pathTo(const SkPath& path) {
969 int i, vcount = path.fVerbs.count();
970 if (vcount == 0) {
971 return;
972 }
973
974 this->incReserve(vcount);
975
976 const uint8_t* verbs = path.fVerbs.begin();
977 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
978
979 SkASSERT(verbs[0] == kMove_Verb);
980 for (i = 1; i < vcount; i++) {
981 switch (verbs[i]) {
982 case kLine_Verb:
983 this->lineTo(pts[0].fX, pts[0].fY);
984 break;
985 case kQuad_Verb:
986 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
987 break;
988 case kCubic_Verb:
989 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
990 pts[2].fX, pts[2].fY);
991 break;
992 case kClose_Verb:
993 return;
994 }
995 pts += gPtsInVerb[verbs[i]];
996 }
997}
998
999// ignore the last point of the 1st contour
1000void SkPath::reversePathTo(const SkPath& path) {
1001 int i, vcount = path.fVerbs.count();
1002 if (vcount == 0) {
1003 return;
1004 }
1005
1006 this->incReserve(vcount);
1007
1008 const uint8_t* verbs = path.fVerbs.begin();
1009 const SkPoint* pts = path.fPts.begin();
1010
1011 SkASSERT(verbs[0] == kMove_Verb);
1012 for (i = 1; i < vcount; i++) {
1013 int n = gPtsInVerb[verbs[i]];
1014 if (n == 0) {
1015 break;
1016 }
1017 pts += n;
1018 }
1019
1020 while (--i > 0) {
1021 switch (verbs[i]) {
1022 case kLine_Verb:
1023 this->lineTo(pts[-1].fX, pts[-1].fY);
1024 break;
1025 case kQuad_Verb:
1026 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1027 break;
1028 case kCubic_Verb:
1029 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1030 pts[-3].fX, pts[-3].fY);
1031 break;
1032 default:
1033 SkASSERT(!"bad verb");
1034 break;
1035 }
1036 pts -= gPtsInVerb[verbs[i]];
1037 }
1038}
1039
1040///////////////////////////////////////////////////////////////////////////////
1041
1042void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1043 SkMatrix matrix;
1044
1045 matrix.setTranslate(dx, dy);
1046 this->transform(matrix, dst);
1047}
1048
1049#include "SkGeometry.h"
1050
1051static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1052 int level = 2) {
1053 if (--level >= 0) {
1054 SkPoint tmp[5];
1055
1056 SkChopQuadAtHalf(pts, tmp);
1057 subdivide_quad_to(path, &tmp[0], level);
1058 subdivide_quad_to(path, &tmp[2], level);
1059 } else {
1060 path->quadTo(pts[1], pts[2]);
1061 }
1062}
1063
1064static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1065 int level = 2) {
1066 if (--level >= 0) {
1067 SkPoint tmp[7];
1068
1069 SkChopCubicAtHalf(pts, tmp);
1070 subdivide_cubic_to(path, &tmp[0], level);
1071 subdivide_cubic_to(path, &tmp[3], level);
1072 } else {
1073 path->cubicTo(pts[1], pts[2], pts[3]);
1074 }
1075}
1076
1077void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1078 SkDEBUGCODE(this->validate();)
1079 if (dst == NULL) {
1080 dst = (SkPath*)this;
1081 }
1082
tomhudson@google.com8d430182011-06-06 19:11:19 +00001083 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 SkPath tmp;
1085 tmp.fFillType = fFillType;
1086
1087 SkPath::Iter iter(*this, false);
1088 SkPoint pts[4];
1089 SkPath::Verb verb;
1090
1091 while ((verb = iter.next(pts)) != kDone_Verb) {
1092 switch (verb) {
1093 case kMove_Verb:
1094 tmp.moveTo(pts[0]);
1095 break;
1096 case kLine_Verb:
1097 tmp.lineTo(pts[1]);
1098 break;
1099 case kQuad_Verb:
1100 subdivide_quad_to(&tmp, pts);
1101 break;
1102 case kCubic_Verb:
1103 subdivide_cubic_to(&tmp, pts);
1104 break;
1105 case kClose_Verb:
1106 tmp.close();
1107 break;
1108 default:
1109 SkASSERT(!"unknown verb");
1110 break;
1111 }
1112 }
1113
1114 dst->swap(tmp);
1115 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1116 } else {
1117 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001118 // fBoundsIsDirty before we set it
1119 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001121 matrix.mapRect(&dst->fBounds, fBounds);
1122 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001124 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001125 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 }
1127
1128 if (this != dst) {
1129 dst->fVerbs = fVerbs;
1130 dst->fPts.setCount(fPts.count());
1131 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001132 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001133 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001134 }
1135 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1136 SkDEBUGCODE(dst->validate();)
1137 }
1138}
1139
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140///////////////////////////////////////////////////////////////////////////////
1141///////////////////////////////////////////////////////////////////////////////
1142
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001143enum SegmentState {
1144 kAfterClose_SegmentState, // We will need a move next, but we have a
1145 // previous close pt to use for the new move.
1146 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1147 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1148 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149};
1150
1151SkPath::Iter::Iter() {
1152#ifdef SK_DEBUG
1153 fPts = NULL;
1154 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001155 fForceClose = fCloseLine = false;
1156 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157#endif
1158 // need to init enough to make next() harmlessly return kDone_Verb
1159 fVerbs = NULL;
1160 fVerbStop = NULL;
1161 fNeedClose = false;
1162}
1163
1164SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1165 this->setPath(path, forceClose);
1166}
1167
1168void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1169 fPts = path.fPts.begin();
1170 fVerbs = path.fVerbs.begin();
1171 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001172 fLastPt.fX = fLastPt.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 fForceClose = SkToU8(forceClose);
1174 fNeedClose = false;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001175 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176}
1177
1178bool SkPath::Iter::isClosedContour() const {
1179 if (fVerbs == NULL || fVerbs == fVerbStop) {
1180 return false;
1181 }
1182 if (fForceClose) {
1183 return true;
1184 }
1185
1186 const uint8_t* verbs = fVerbs;
1187 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001188
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 if (kMove_Verb == *verbs) {
1190 verbs += 1; // skip the initial moveto
1191 }
1192
1193 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001194 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195 if (kMove_Verb == v) {
1196 break;
1197 }
1198 if (kClose_Verb == v) {
1199 return true;
1200 }
1201 }
1202 return false;
1203}
1204
1205SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1206 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001207 // A special case: if both points are NaN, SkPoint::operation== returns
1208 // false, but the iterator expects that they are treated as the same.
1209 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001210 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1211 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001212 return kClose_Verb;
1213 }
1214
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 if (pts) {
1216 pts[0] = fLastPt;
1217 pts[1] = fMoveTo;
1218 }
1219 fLastPt = fMoveTo;
1220 fCloseLine = true;
1221 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001222 } else {
1223 pts[0] = fMoveTo;
1224 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226}
1227
1228bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001229 if (fSegmentState == kAfterClose_SegmentState) {
1230 // We have closed a curve and have a primitive, so we need a move.
1231 // Set the first return pt to the most recent move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 if (pts) {
1233 *pts = fMoveTo;
1234 }
1235 fNeedClose = fForceClose;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001236 fSegmentState = kAfterMove_SegmentState;
1237 fVerbs -= 1; // Step back to see the primitive again
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 return true;
1239 }
1240
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001241 if (fSegmentState == kAfterMove_SegmentState) {
1242 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 if (pts) {
1244 *pts = fMoveTo;
1245 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001246 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001247 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001248 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1249 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250 if (pts) {
1251 *pts = fPts[-1];
1252 }
1253 }
1254 return false;
1255}
1256
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001257void SkPath::Iter::consumeDegenerateSegments() {
1258 // We need to step over anything that will not move the current draw point
1259 // forward before the next move is seen
1260 const uint8_t* lastMoveVerb = 0;
1261 const SkPoint* lastMovePt = 0;
1262 SkPoint lastPt = fLastPt;
1263 while (fVerbs != fVerbStop) {
1264 unsigned verb = *fVerbs;
1265 switch (verb) {
1266 case kMove_Verb:
1267 // Set state for the next method.
1268 fSegmentState = kAfterMove_SegmentState;
1269 fMoveTo = fPts[0];
1270
1271 // Keep a record of this most recent move
1272 lastMoveVerb = fVerbs;
1273 lastMovePt = fPts;
1274 lastPt = fPts[0];
1275 fVerbs++;
1276 fPts++;
1277 break;
1278
1279 case kClose_Verb:
1280 // A close when we are in a segment is always valid
1281 if (fSegmentState == kAfterPrimitive_SegmentState) {
1282 return;
1283 }
1284 // A close at any other time must be ignored
1285 fVerbs++;
1286 break;
1287
1288 case kLine_Verb:
1289 if (!IsLineDegenerate(lastPt, fPts[0])) {
1290 if (lastMoveVerb) {
1291 fVerbs = lastMoveVerb;
1292 fPts = lastMovePt;
1293 return;
1294 }
1295 return;
1296 }
1297 // Ignore this line and continue
1298 fVerbs++;
1299 fPts++;
1300 break;
1301
1302 case kQuad_Verb:
1303 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1304 if (lastMoveVerb) {
1305 fVerbs = lastMoveVerb;
1306 fPts = lastMovePt;
1307 return;
1308 }
1309 return;
1310 }
1311 // Ignore this line and continue
1312 fVerbs++;
1313 fPts += 2;
1314 break;
1315
1316 case kCubic_Verb:
1317 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1318 if (lastMoveVerb) {
1319 fVerbs = lastMoveVerb;
1320 fPts = lastMovePt;
1321 return;
1322 }
1323 return;
1324 }
1325 // Ignore this line and continue
1326 fVerbs++;
1327 fPts += 3;
1328 break;
1329
1330 default:
1331 SkASSERT(false && "Should never see kDone_Verb");
1332 }
1333 }
1334}
1335
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001337 this->consumeDegenerateSegments();
1338
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001340 // Close the curve if requested and if there is some curve to close
1341 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 if (kLine_Verb == this->autoClose(pts)) {
1343 return kLine_Verb;
1344 }
1345 fNeedClose = false;
1346 return kClose_Verb;
1347 }
1348 return kDone_Verb;
1349 }
1350
1351 unsigned verb = *fVerbs++;
1352 const SkPoint* srcPts = fPts;
1353
1354 switch (verb) {
1355 case kMove_Verb:
1356 if (fNeedClose) {
1357 fVerbs -= 1;
1358 verb = this->autoClose(pts);
1359 if (verb == kClose_Verb) {
1360 fNeedClose = false;
1361 }
1362 return (Verb)verb;
1363 }
1364 if (fVerbs == fVerbStop) { // might be a trailing moveto
1365 return kDone_Verb;
1366 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001367 if (pts) {
1368 pts[0] = *srcPts;
1369 }
1370 srcPts += 1;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001371 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372 fNeedClose = fForceClose;
1373 break;
1374 case kLine_Verb:
1375 if (this->cons_moveTo(pts)) {
1376 return kMove_Verb;
1377 }
1378 if (pts) {
1379 pts[1] = srcPts[0];
1380 }
1381 fLastPt = srcPts[0];
1382 fCloseLine = false;
1383 srcPts += 1;
1384 break;
1385 case kQuad_Verb:
1386 if (this->cons_moveTo(pts)) {
1387 return kMove_Verb;
1388 }
1389 if (pts) {
1390 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1391 }
1392 fLastPt = srcPts[1];
1393 srcPts += 2;
1394 break;
1395 case kCubic_Verb:
1396 if (this->cons_moveTo(pts)) {
1397 return kMove_Verb;
1398 }
1399 if (pts) {
1400 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1401 }
1402 fLastPt = srcPts[2];
1403 srcPts += 3;
1404 break;
1405 case kClose_Verb:
1406 verb = this->autoClose(pts);
1407 if (verb == kLine_Verb) {
1408 fVerbs -= 1;
1409 } else {
1410 fNeedClose = false;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001411 fSegmentState = kAfterClose_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001413 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 break;
1415 }
1416 fPts = srcPts;
1417 return (Verb)verb;
1418}
1419
1420///////////////////////////////////////////////////////////////////////////////
1421
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422/*
1423 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1424*/
1425
reed@google.com73945652011-04-25 19:04:27 +00001426void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 SkDEBUGCODE(this->validate();)
1428
1429 buffer.write32(fPts.count());
1430 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001431 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1433 buffer.writePad(fVerbs.begin(), fVerbs.count());
1434}
1435
reed@google.com73945652011-04-25 19:04:27 +00001436void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 fPts.setCount(buffer.readS32());
1438 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001439 uint32_t packed = buffer.readS32();
1440 fFillType = packed >> 8;
1441 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1443 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001444
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001445 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001446 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447
1448 SkDEBUGCODE(this->validate();)
1449}
1450
1451///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452///////////////////////////////////////////////////////////////////////////////
1453
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454void SkPath::dump(bool forceClose, const char title[]) const {
1455 Iter iter(*this, forceClose);
1456 SkPoint pts[4];
1457 Verb verb;
1458
1459 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1460 title ? title : "");
1461
1462 while ((verb = iter.next(pts)) != kDone_Verb) {
1463 switch (verb) {
1464 case kMove_Verb:
1465#ifdef SK_CAN_USE_FLOAT
1466 SkDebugf(" path: moveTo [%g %g]\n",
1467 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1468#else
1469 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1470#endif
1471 break;
1472 case kLine_Verb:
1473#ifdef SK_CAN_USE_FLOAT
1474 SkDebugf(" path: lineTo [%g %g]\n",
1475 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1476#else
1477 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1478#endif
1479 break;
1480 case kQuad_Verb:
1481#ifdef SK_CAN_USE_FLOAT
1482 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1483 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1484 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1485#else
1486 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1487 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1488#endif
1489 break;
1490 case kCubic_Verb:
1491#ifdef SK_CAN_USE_FLOAT
1492 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1493 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1494 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1495 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1496#else
1497 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1498 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1499 pts[3].fX, pts[3].fY);
1500#endif
1501 break;
1502 case kClose_Verb:
1503 SkDebugf(" path: close\n");
1504 break;
1505 default:
1506 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1507 verb = kDone_Verb; // stop the loop
1508 break;
1509 }
1510 }
1511 SkDebugf("path: done %s\n", title ? title : "");
1512}
1513
reed@android.come522ca52009-11-23 20:10:41 +00001514void SkPath::dump() const {
1515 this->dump(false);
1516}
1517
1518#ifdef SK_DEBUG
1519void SkPath::validate() const {
1520 SkASSERT(this != NULL);
1521 SkASSERT((fFillType & ~3) == 0);
1522 fPts.validate();
1523 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001524
reed@android.come522ca52009-11-23 20:10:41 +00001525 if (!fBoundsIsDirty) {
1526 SkRect bounds;
1527 compute_pt_bounds(&bounds, fPts);
1528 if (fPts.count() <= 1) {
1529 // if we're empty, fBounds may be empty but translated, so we can't
1530 // necessarily compare to bounds directly
1531 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1532 // be [2, 2, 2, 2]
1533 SkASSERT(bounds.isEmpty());
1534 SkASSERT(fBounds.isEmpty());
1535 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001536 if (bounds.isEmpty()) {
1537 SkASSERT(fBounds.isEmpty());
1538 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001539 if (!fBounds.isEmpty()) {
1540 SkASSERT(fBounds.contains(bounds));
1541 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001542 }
reed@android.come522ca52009-11-23 20:10:41 +00001543 }
1544 }
reed@google.com10296cc2011-09-21 12:29:05 +00001545
1546 uint32_t mask = 0;
1547 for (int i = 0; i < fVerbs.count(); i++) {
1548 switch (fVerbs[i]) {
1549 case kLine_Verb:
1550 mask |= kLine_SegmentMask;
1551 break;
1552 case kQuad_Verb:
1553 mask |= kQuad_SegmentMask;
1554 break;
1555 case kCubic_Verb:
1556 mask |= kCubic_SegmentMask;
1557 }
1558 }
1559 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001560}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001561#endif
reed@android.come522ca52009-11-23 20:10:41 +00001562
reed@google.com04863fa2011-05-15 04:08:24 +00001563///////////////////////////////////////////////////////////////////////////////
1564
reed@google.com0b7b9822011-05-16 12:29:27 +00001565static int sign(SkScalar x) { return x < 0; }
1566#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001567
reed@google.com04863fa2011-05-15 04:08:24 +00001568static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001569 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001570}
1571
1572// only valid for a single contour
1573struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001574 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001575 fSign = 0;
1576 // warnings
1577 fCurrPt.set(0, 0);
1578 fVec0.set(0, 0);
1579 fVec1.set(0, 0);
1580 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001581
1582 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001583 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001584 }
1585
1586 SkPath::Convexity getConvexity() const { return fConvexity; }
1587
1588 void addPt(const SkPoint& pt) {
1589 if (SkPath::kConcave_Convexity == fConvexity) {
1590 return;
1591 }
1592
1593 if (0 == fPtCount) {
1594 fCurrPt = pt;
1595 ++fPtCount;
1596 } else {
1597 SkVector vec = pt - fCurrPt;
1598 if (vec.fX || vec.fY) {
1599 fCurrPt = pt;
1600 if (++fPtCount == 2) {
1601 fFirstVec = fVec1 = vec;
1602 } else {
1603 SkASSERT(fPtCount > 2);
1604 this->addVec(vec);
1605 }
reed@google.com85b6e392011-05-15 20:25:17 +00001606
1607 int sx = sign(vec.fX);
1608 int sy = sign(vec.fY);
1609 fDx += (sx != fSx);
1610 fDy += (sy != fSy);
1611 fSx = sx;
1612 fSy = sy;
1613
1614 if (fDx > 3 || fDy > 3) {
1615 fConvexity = SkPath::kConcave_Convexity;
1616 }
reed@google.com04863fa2011-05-15 04:08:24 +00001617 }
1618 }
1619 }
1620
1621 void close() {
1622 if (fPtCount > 2) {
1623 this->addVec(fFirstVec);
1624 }
1625 }
1626
1627private:
1628 void addVec(const SkVector& vec) {
1629 SkASSERT(vec.fX || vec.fY);
1630 fVec0 = fVec1;
1631 fVec1 = vec;
1632 int sign = CrossProductSign(fVec0, fVec1);
1633 if (0 == fSign) {
1634 fSign = sign;
1635 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001636 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001637 fConvexity = SkPath::kConcave_Convexity;
1638 }
1639 }
1640 }
1641
1642 SkPoint fCurrPt;
1643 SkVector fVec0, fVec1, fFirstVec;
1644 int fPtCount; // non-degenerate points
1645 int fSign;
1646 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001647 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001648};
1649
1650SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1651 SkPoint pts[4];
1652 SkPath::Verb verb;
1653 SkPath::Iter iter(path, true);
1654
1655 int contourCount = 0;
1656 int count;
1657 Convexicator state;
1658
1659 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1660 switch (verb) {
1661 case kMove_Verb:
1662 if (++contourCount > 1) {
1663 return kConcave_Convexity;
1664 }
1665 pts[1] = pts[0];
1666 count = 1;
1667 break;
1668 case kLine_Verb: count = 1; break;
1669 case kQuad_Verb: count = 2; break;
1670 case kCubic_Verb: count = 3; break;
1671 case kClose_Verb:
1672 state.close();
1673 count = 0;
1674 break;
1675 default:
1676 SkASSERT(!"bad verb");
1677 return kConcave_Convexity;
1678 }
1679
1680 for (int i = 1; i <= count; i++) {
1681 state.addPt(pts[i]);
1682 }
1683 // early exit
1684 if (kConcave_Convexity == state.getConvexity()) {
1685 return kConcave_Convexity;
1686 }
1687 }
1688 return state.getConvexity();
1689}