blob: 10569fd88333288523abc8892477b02baafc8f07 [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
17/* This guy's constructor/destructor bracket a path editing operation. It is
18 used when we know the bounds of the amount we are going to add to the path
19 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000020
reed@android.com8a1c16f2008-12-17 15:59:43 +000021 It captures some state about the path up front (i.e. if it already has a
22 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000023 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000024
reed@android.com6b82d1a2009-06-03 02:35:01 +000025 It also notes if the path was originally empty, and if so, sets isConvex
26 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000027 */
28class SkAutoPathBoundsUpdate {
29public:
30 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
31 this->init(path);
32 }
33
34 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
35 SkScalar right, SkScalar bottom) {
36 fRect.set(left, top, right, bottom);
37 this->init(path);
38 }
reed@google.comabf15c12011-01-18 20:35:51 +000039
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000041 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000042 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000043 fPath->fBounds = fRect;
44 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000045 } else if (!fDirty) {
reed@android.comd252db02009-04-01 18:31:44 +000046 fPath->fBounds.join(fRect);
47 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 }
49 }
reed@google.comabf15c12011-01-18 20:35:51 +000050
reed@android.com8a1c16f2008-12-17 15:59:43 +000051private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000052 SkPath* fPath;
53 SkRect fRect;
54 bool fDirty;
55 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000058 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000060 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000062 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000063 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 }
65};
66
reed@android.comd252db02009-04-01 18:31:44 +000067static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
69 bounds->set(0, 0, 0, 0);
70 } else {
71 bounds->set(pts.begin(), pts.count());
72// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
73 }
74}
75
76////////////////////////////////////////////////////////////////////////////
77
78/*
79 Stores the verbs and points as they are given to us, with exceptions:
80 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
81 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
82
83 The iterator does more cleanup, especially if forceClose == true
84 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
85 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
86 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
87*/
88
89////////////////////////////////////////////////////////////////////////////
90
bsalomon@google.com2ec72802011-09-21 21:46:03 +000091SkPath::SkPath()
92 : fFillType(kWinding_FillType)
93 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +000094 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +000095 fSegmentMask = 0;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +000096#ifdef ANDROID
97 fGenerationID = 0;
98#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +000099}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100
101SkPath::SkPath(const SkPath& src) {
102 SkDEBUGCODE(src.validate();)
103 *this = src;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000104#ifdef ANDROID
105 // the assignment operator above increments the ID so correct for that here
106 fGenerationID--;
107#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108}
109
110SkPath::~SkPath() {
111 SkDEBUGCODE(this->validate();)
112}
113
114SkPath& SkPath::operator=(const SkPath& src) {
115 SkDEBUGCODE(src.validate();)
116
117 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000118 fBounds = src.fBounds;
119 fPts = src.fPts;
120 fVerbs = src.fVerbs;
121 fFillType = src.fFillType;
122 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000123 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000124 fSegmentMask = src.fSegmentMask;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000125 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 }
127 SkDEBUGCODE(this->validate();)
128 return *this;
129}
130
reed@android.com3abec1d2009-03-02 05:36:20 +0000131bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000132 // note: don't need to look at isConvex or bounds, since just comparing the
133 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000134
135 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
136 // since it is only a cache of info in the fVerbs, but its a fast way to
137 // notice a difference
138
reed@android.com3abec1d2009-03-02 05:36:20 +0000139 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000140 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
141 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000142}
143
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144void SkPath::swap(SkPath& other) {
145 SkASSERT(&other != NULL);
146
147 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000148 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149 fPts.swap(other.fPts);
150 fVerbs.swap(other.fVerbs);
151 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000152 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000153 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000154 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000155 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156 }
157}
158
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000159#ifdef ANDROID
160uint32_t SkPath::getGenerationID() const {
161 return fGenerationID;
162}
163#endif
164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165void SkPath::reset() {
166 SkDEBUGCODE(this->validate();)
167
168 fPts.reset();
169 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000170 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000171 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000172 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000173 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174}
175
176void SkPath::rewind() {
177 SkDEBUGCODE(this->validate();)
178
179 fPts.rewind();
180 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000181 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000182 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000183 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000184 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185}
186
187bool SkPath::isEmpty() const {
188 SkDEBUGCODE(this->validate();)
189
190 int count = fVerbs.count();
191 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
192}
193
caryclark@google.comf1316942011-07-26 19:54:45 +0000194/*
195 Determines if path is a rect by keeping track of changes in direction
196 and looking for a loop either clockwise or counterclockwise.
197
198 The direction is computed such that:
199 0: vertical up
200 1: horizontal right
201 2: vertical down
202 3: horizontal left
203
204A rectangle cycles up/right/down/left or up/left/down/right.
205
206The test fails if:
207 The path is closed, and followed by a line.
208 A second move creates a new endpoint.
209 A diagonal line is parsed.
210 There's more than four changes of direction.
211 There's a discontinuity on the line (e.g., a move in the middle)
212 The line reverses direction.
213 The rectangle doesn't complete a cycle.
214 The path contains a quadratic or cubic.
215 The path contains fewer than four points.
216 The final point isn't equal to the first point.
217
218It's OK if the path has:
219 Several colinear line segments composing a rectangle side.
220 Single points on the rectangle side.
221
222The direction takes advantage of the corners found since opposite sides
223must travel in opposite directions.
224
225FIXME: Allow colinear quads and cubics to be treated like lines.
226FIXME: If the API passes fill-only, return true if the filled stroke
227 is a rectangle, though the caller failed to close the path.
228 */
229bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000231
caryclark@google.comf1316942011-07-26 19:54:45 +0000232 int corners = 0;
233 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000234 first.set(0, 0);
235 last.set(0, 0);
236 int firstDirection = 0;
237 int lastDirection = 0;
238 int nextDirection = 0;
239 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000240 bool autoClose = false;
241 const uint8_t* verbs = fVerbs.begin();
242 const uint8_t* verbStop = fVerbs.end();
243 const SkPoint* pts = fPts.begin();
244 while (verbs != verbStop) {
245 switch (*verbs++) {
246 case kClose_Verb:
247 pts = fPts.begin();
248 autoClose = true;
249 case kLine_Verb: {
250 SkScalar left = last.fX;
251 SkScalar top = last.fY;
252 SkScalar right = pts->fX;
253 SkScalar bottom = pts->fY;
254 ++pts;
255 if (left != right && top != bottom) {
256 return false; // diagonal
257 }
258 if (left == right && top == bottom) {
259 break; // single point on side OK
260 }
261 nextDirection = (left != right) << 0 |
262 (left < right || top < bottom) << 1;
263 if (0 == corners) {
264 firstDirection = nextDirection;
265 first = last;
266 last = pts[-1];
267 corners = 1;
268 closedOrMoved = false;
269 break;
270 }
271 if (closedOrMoved) {
272 return false; // closed followed by a line
273 }
274 closedOrMoved = autoClose;
275 if (lastDirection != nextDirection) {
276 if (++corners > 4) {
277 return false; // too many direction changes
278 }
279 }
280 last = pts[-1];
281 if (lastDirection == nextDirection) {
282 break; // colinear segment
283 }
284 // Possible values for corners are 2, 3, and 4.
285 // When corners == 3, nextDirection opposes firstDirection.
286 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000287 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000288 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
289 if ((directionCycle ^ turn) != nextDirection) {
290 return false; // direction didn't follow cycle
291 }
292 break;
293 }
294 case kQuad_Verb:
295 case kCubic_Verb:
296 return false; // quadratic, cubic not allowed
297 case kMove_Verb:
298 last = *pts++;
299 closedOrMoved = true;
300 break;
301 }
302 lastDirection = nextDirection;
303 }
304 // Success if 4 corners and first point equals last
305 bool result = 4 == corners && first == last;
306 if (result && rect) {
307 *rect = getBounds();
308 }
309 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312int SkPath::getPoints(SkPoint copy[], int max) const {
313 SkDEBUGCODE(this->validate();)
314
315 SkASSERT(max >= 0);
316 int count = fPts.count();
317 if (copy && max > 0 && count > 0) {
318 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
319 }
320 return count;
321}
322
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000323SkPoint SkPath::getPoint(int index) const {
324 if ((unsigned)index < (unsigned)fPts.count()) {
325 return fPts[index];
326 }
327 return SkPoint::Make(0, 0);
328}
329
reed@google.com294dd7b2011-10-11 11:58:32 +0000330bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 SkDEBUGCODE(this->validate();)
332
reed@google.com294dd7b2011-10-11 11:58:32 +0000333 int count = fPts.count();
334 if (count > 0) {
335 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 *lastPt = fPts[count - 1];
337 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000338 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000340 if (lastPt) {
341 lastPt->set(0, 0);
342 }
343 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344}
345
346void SkPath::setLastPt(SkScalar x, SkScalar y) {
347 SkDEBUGCODE(this->validate();)
348
349 int count = fPts.count();
350 if (count == 0) {
351 this->moveTo(x, y);
352 } else {
353 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000354 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
356}
357
reed@android.comd252db02009-04-01 18:31:44 +0000358void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000360 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361
reed@android.comd252db02009-04-01 18:31:44 +0000362 fBoundsIsDirty = false;
363 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364}
365
reed@google.com04863fa2011-05-15 04:08:24 +0000366void SkPath::setConvexity(Convexity c) {
367 if (fConvexity != c) {
368 fConvexity = c;
369 GEN_ID_INC;
370 }
371}
372
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373//////////////////////////////////////////////////////////////////////////////
374// Construction methods
375
reed@google.comb54455e2011-05-16 14:16:04 +0000376#define DIRTY_AFTER_EDIT \
377 do { \
378 fBoundsIsDirty = true; \
379 fConvexity = kUnknown_Convexity;\
380 } while (0)
381
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382void SkPath::incReserve(U16CPU inc) {
383 SkDEBUGCODE(this->validate();)
384
385 fVerbs.setReserve(fVerbs.count() + inc);
386 fPts.setReserve(fPts.count() + inc);
387
388 SkDEBUGCODE(this->validate();)
389}
390
391void SkPath::moveTo(SkScalar x, SkScalar y) {
392 SkDEBUGCODE(this->validate();)
393
394 int vc = fVerbs.count();
395 SkPoint* pt;
396
397 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
398 pt = &fPts[fPts.count() - 1];
399 } else {
400 pt = fPts.append();
401 *fVerbs.append() = kMove_Verb;
402 }
403 pt->set(x, y);
404
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000405 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000406 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407}
408
409void SkPath::rMoveTo(SkScalar x, SkScalar y) {
410 SkPoint pt;
411 this->getLastPt(&pt);
412 this->moveTo(pt.fX + x, pt.fY + y);
413}
414
415void SkPath::lineTo(SkScalar x, SkScalar y) {
416 SkDEBUGCODE(this->validate();)
417
418 if (fVerbs.count() == 0) {
419 fPts.append()->set(0, 0);
420 *fVerbs.append() = kMove_Verb;
421 }
422 fPts.append()->set(x, y);
423 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000424 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425
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::rLineTo(SkScalar x, SkScalar y) {
431 SkPoint pt;
432 this->getLastPt(&pt);
433 this->lineTo(pt.fX + x, pt.fY + y);
434}
435
436void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
437 SkDEBUGCODE(this->validate();)
438
439 if (fVerbs.count() == 0) {
440 fPts.append()->set(0, 0);
441 *fVerbs.append() = kMove_Verb;
442 }
443
444 SkPoint* pts = fPts.append(2);
445 pts[0].set(x1, y1);
446 pts[1].set(x2, y2);
447 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000448 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000450 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000451 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452}
453
454void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
455 SkPoint pt;
456 this->getLastPt(&pt);
457 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
458}
459
460void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
461 SkScalar x3, SkScalar y3) {
462 SkDEBUGCODE(this->validate();)
463
464 if (fVerbs.count() == 0) {
465 fPts.append()->set(0, 0);
466 *fVerbs.append() = kMove_Verb;
467 }
468 SkPoint* pts = fPts.append(3);
469 pts[0].set(x1, y1);
470 pts[1].set(x2, y2);
471 pts[2].set(x3, y3);
472 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000473 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000475 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000476 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477}
478
479void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
480 SkScalar x3, SkScalar y3) {
481 SkPoint pt;
482 this->getLastPt(&pt);
483 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
484 pt.fX + x3, pt.fY + y3);
485}
486
487void SkPath::close() {
488 SkDEBUGCODE(this->validate();)
489
490 int count = fVerbs.count();
491 if (count > 0) {
492 switch (fVerbs[count - 1]) {
493 case kLine_Verb:
494 case kQuad_Verb:
495 case kCubic_Verb:
496 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000497 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498 break;
499 default:
500 // don't add a close if the prev wasn't a primitive
501 break;
502 }
503 }
504}
505
506///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000507
reed@android.com8a1c16f2008-12-17 15:59:43 +0000508void SkPath::addRect(const SkRect& rect, Direction dir) {
509 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
510}
511
512void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
513 SkScalar bottom, Direction dir) {
514 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
515
516 this->incReserve(5);
517
518 this->moveTo(left, top);
519 if (dir == kCCW_Direction) {
520 this->lineTo(left, bottom);
521 this->lineTo(right, bottom);
522 this->lineTo(right, top);
523 } else {
524 this->lineTo(right, top);
525 this->lineTo(right, bottom);
526 this->lineTo(left, bottom);
527 }
528 this->close();
529}
530
531#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
532
533void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
534 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 SkScalar w = rect.width();
536 SkScalar halfW = SkScalarHalf(w);
537 SkScalar h = rect.height();
538 SkScalar halfH = SkScalarHalf(h);
539
540 if (halfW <= 0 || halfH <= 0) {
541 return;
542 }
543
reed@google.comabf15c12011-01-18 20:35:51 +0000544 bool skip_hori = rx >= halfW;
545 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546
547 if (skip_hori && skip_vert) {
548 this->addOval(rect, dir);
549 return;
550 }
reed@google.comabf15c12011-01-18 20:35:51 +0000551
552 SkAutoPathBoundsUpdate apbu(this, rect);
553
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554 if (skip_hori) {
555 rx = halfW;
556 } else if (skip_vert) {
557 ry = halfH;
558 }
559
560 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
561 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
562
563 this->incReserve(17);
564 this->moveTo(rect.fRight - rx, rect.fTop);
565 if (dir == kCCW_Direction) {
566 if (!skip_hori) {
567 this->lineTo(rect.fLeft + rx, rect.fTop); // top
568 }
569 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
570 rect.fLeft, rect.fTop + ry - sy,
571 rect.fLeft, rect.fTop + ry); // top-left
572 if (!skip_vert) {
573 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
574 }
575 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
576 rect.fLeft + rx - sx, rect.fBottom,
577 rect.fLeft + rx, rect.fBottom); // bot-left
578 if (!skip_hori) {
579 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
580 }
581 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
582 rect.fRight, rect.fBottom - ry + sy,
583 rect.fRight, rect.fBottom - ry); // bot-right
584 if (!skip_vert) {
585 this->lineTo(rect.fRight, rect.fTop + ry);
586 }
587 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
588 rect.fRight - rx + sx, rect.fTop,
589 rect.fRight - rx, rect.fTop); // top-right
590 } else {
591 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
592 rect.fRight, rect.fTop + ry - sy,
593 rect.fRight, rect.fTop + ry); // top-right
594 if (!skip_vert) {
595 this->lineTo(rect.fRight, rect.fBottom - ry);
596 }
597 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
598 rect.fRight - rx + sx, rect.fBottom,
599 rect.fRight - rx, rect.fBottom); // bot-right
600 if (!skip_hori) {
601 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
602 }
603 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
604 rect.fLeft, rect.fBottom - ry + sy,
605 rect.fLeft, rect.fBottom - ry); // bot-left
606 if (!skip_vert) {
607 this->lineTo(rect.fLeft, rect.fTop + ry); // left
608 }
609 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
610 rect.fLeft + rx - sx, rect.fTop,
611 rect.fLeft + rx, rect.fTop); // top-left
612 if (!skip_hori) {
613 this->lineTo(rect.fRight - rx, rect.fTop); // top
614 }
615 }
616 this->close();
617}
618
619static void add_corner_arc(SkPath* path, const SkRect& rect,
620 SkScalar rx, SkScalar ry, int startAngle,
621 SkPath::Direction dir, bool forceMoveTo) {
622 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
623 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000624
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 SkRect r;
626 r.set(-rx, -ry, rx, ry);
627
628 switch (startAngle) {
629 case 0:
630 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
631 break;
632 case 90:
633 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
634 break;
635 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
636 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
637 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
638 }
reed@google.comabf15c12011-01-18 20:35:51 +0000639
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 SkScalar start = SkIntToScalar(startAngle);
641 SkScalar sweep = SkIntToScalar(90);
642 if (SkPath::kCCW_Direction == dir) {
643 start += sweep;
644 sweep = -sweep;
645 }
reed@google.comabf15c12011-01-18 20:35:51 +0000646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 path->arcTo(r, start, sweep, forceMoveTo);
648}
649
650void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
651 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000652 // abort before we invoke SkAutoPathBoundsUpdate()
653 if (rect.isEmpty()) {
654 return;
655 }
656
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 SkAutoPathBoundsUpdate apbu(this, rect);
658
659 if (kCW_Direction == dir) {
660 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
661 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
662 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
663 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
664 } else {
665 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
666 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
667 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
668 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
669 }
670 this->close();
671}
672
673void SkPath::addOval(const SkRect& oval, Direction dir) {
674 SkAutoPathBoundsUpdate apbu(this, oval);
675
676 SkScalar cx = oval.centerX();
677 SkScalar cy = oval.centerY();
678 SkScalar rx = SkScalarHalf(oval.width());
679 SkScalar ry = SkScalarHalf(oval.height());
680#if 0 // these seem faster than using quads (1/2 the number of edges)
681 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
682 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
683
684 this->incReserve(13);
685 this->moveTo(cx + rx, cy);
686 if (dir == kCCW_Direction) {
687 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
688 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
689 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
690 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
691 } else {
692 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
693 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
694 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
695 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
696 }
697#else
698 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
699 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
700 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
701 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
702
703 /*
704 To handle imprecision in computing the center and radii, we revert to
705 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
706 to ensure that we don't exceed the oval's bounds *ever*, since we want
707 to use oval for our fast-bounds, rather than have to recompute it.
708 */
709 const SkScalar L = oval.fLeft; // cx - rx
710 const SkScalar T = oval.fTop; // cy - ry
711 const SkScalar R = oval.fRight; // cx + rx
712 const SkScalar B = oval.fBottom; // cy + ry
713
714 this->incReserve(17); // 8 quads + close
715 this->moveTo(R, cy);
716 if (dir == kCCW_Direction) {
717 this->quadTo( R, cy - sy, cx + mx, cy - my);
718 this->quadTo(cx + sx, T, cx , T);
719 this->quadTo(cx - sx, T, cx - mx, cy - my);
720 this->quadTo( L, cy - sy, L, cy );
721 this->quadTo( L, cy + sy, cx - mx, cy + my);
722 this->quadTo(cx - sx, B, cx , B);
723 this->quadTo(cx + sx, B, cx + mx, cy + my);
724 this->quadTo( R, cy + sy, R, cy );
725 } else {
726 this->quadTo( R, cy + sy, cx + mx, cy + my);
727 this->quadTo(cx + sx, B, cx , B);
728 this->quadTo(cx - sx, B, 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, T, cx , T);
732 this->quadTo(cx + sx, T, cx + mx, cy - my);
733 this->quadTo( R, cy - sy, R, cy );
734 }
735#endif
736 this->close();
737}
738
739void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
740 if (r > 0) {
741 SkRect rect;
742 rect.set(x - r, y - r, x + r, y + r);
743 this->addOval(rect, dir);
744 }
745}
746
747#include "SkGeometry.h"
748
749static int build_arc_points(const SkRect& oval, SkScalar startAngle,
750 SkScalar sweepAngle,
751 SkPoint pts[kSkBuildQuadArcStorage]) {
752 SkVector start, stop;
753
754 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
755 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
756 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000757
758 /* If the sweep angle is nearly (but less than) 360, then due to precision
759 loss in radians-conversion and/or sin/cos, we may end up with coincident
760 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
761 of drawing a nearly complete circle (good).
762 e.g. canvas.drawArc(0, 359.99, ...)
763 -vs- canvas.drawArc(0, 359.9, ...)
764 We try to detect this edge case, and tweak the stop vector
765 */
766 if (start == stop) {
767 SkScalar sw = SkScalarAbs(sweepAngle);
768 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
769 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
770 // make a guess at a tiny angle (in radians) to tweak by
771 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
772 // not sure how much will be enough, so we use a loop
773 do {
774 stopRad -= deltaRad;
775 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
776 } while (start == stop);
777 }
778 }
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000781
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
783 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000784
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 return SkBuildQuadArc(start, stop,
786 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
787 &matrix, pts);
788}
789
790void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
791 bool forceMoveTo) {
792 if (oval.width() < 0 || oval.height() < 0) {
793 return;
794 }
795
796 SkPoint pts[kSkBuildQuadArcStorage];
797 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
798 SkASSERT((count & 1) == 1);
799
800 if (fVerbs.count() == 0) {
801 forceMoveTo = true;
802 }
803 this->incReserve(count);
804 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
805 for (int i = 1; i < count; i += 2) {
806 this->quadTo(pts[i], pts[i+1]);
807 }
808}
809
810void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
811 SkScalar sweepAngle) {
812 if (oval.isEmpty() || 0 == sweepAngle) {
813 return;
814 }
815
816 const SkScalar kFullCircleAngle = SkIntToScalar(360);
817
818 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
819 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
820 return;
821 }
822
823 SkPoint pts[kSkBuildQuadArcStorage];
824 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
825
826 this->incReserve(count);
827 this->moveTo(pts[0]);
828 for (int i = 1; i < count; i += 2) {
829 this->quadTo(pts[i], pts[i+1]);
830 }
831}
832
833/*
834 Need to handle the case when the angle is sharp, and our computed end-points
835 for the arc go behind pt1 and/or p2...
836*/
837void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
838 SkScalar radius) {
839 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 // need to know our prev pt so we can construct tangent vectors
842 {
843 SkPoint start;
844 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000845 // Handle degenerate cases by adding a line to the first point and
846 // bailing out.
847 if ((x1 == start.fX && y1 == start.fY) ||
848 (x1 == x2 && y1 == y2) ||
849 radius == 0) {
850 this->lineTo(x1, y1);
851 return;
852 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 before.setNormalize(x1 - start.fX, y1 - start.fY);
854 after.setNormalize(x2 - x1, y2 - y1);
855 }
reed@google.comabf15c12011-01-18 20:35:51 +0000856
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 SkScalar cosh = SkPoint::DotProduct(before, after);
858 SkScalar sinh = SkPoint::CrossProduct(before, after);
859
860 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000861 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 return;
863 }
reed@google.comabf15c12011-01-18 20:35:51 +0000864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
866 if (dist < 0) {
867 dist = -dist;
868 }
869
870 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
871 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
872 SkRotationDirection arcDir;
873
874 // now turn before/after into normals
875 if (sinh > 0) {
876 before.rotateCCW();
877 after.rotateCCW();
878 arcDir = kCW_SkRotationDirection;
879 } else {
880 before.rotateCW();
881 after.rotateCW();
882 arcDir = kCCW_SkRotationDirection;
883 }
884
885 SkMatrix matrix;
886 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000887
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 matrix.setScale(radius, radius);
889 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
890 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000891
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000893
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 this->incReserve(count);
895 // [xx,yy] == pts[0]
896 this->lineTo(xx, yy);
897 for (int i = 1; i < count; i += 2) {
898 this->quadTo(pts[i], pts[i+1]);
899 }
900}
901
902///////////////////////////////////////////////////////////////////////////////
903
904void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
905 SkMatrix matrix;
906
907 matrix.setTranslate(dx, dy);
908 this->addPath(path, matrix);
909}
910
911void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
912 this->incReserve(path.fPts.count());
913
914 Iter iter(path, false);
915 SkPoint pts[4];
916 Verb verb;
917
918 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
919
920 while ((verb = iter.next(pts)) != kDone_Verb) {
921 switch (verb) {
922 case kMove_Verb:
923 proc(matrix, &pts[0], &pts[0], 1);
924 this->moveTo(pts[0]);
925 break;
926 case kLine_Verb:
927 proc(matrix, &pts[1], &pts[1], 1);
928 this->lineTo(pts[1]);
929 break;
930 case kQuad_Verb:
931 proc(matrix, &pts[1], &pts[1], 2);
932 this->quadTo(pts[1], pts[2]);
933 break;
934 case kCubic_Verb:
935 proc(matrix, &pts[1], &pts[1], 3);
936 this->cubicTo(pts[1], pts[2], pts[3]);
937 break;
938 case kClose_Verb:
939 this->close();
940 break;
941 default:
942 SkASSERT(!"unknown verb");
943 }
944 }
945}
946
947///////////////////////////////////////////////////////////////////////////////
948
949static const uint8_t gPtsInVerb[] = {
950 1, // kMove
951 1, // kLine
952 2, // kQuad
953 3, // kCubic
954 0, // kClose
955 0 // kDone
956};
957
958// ignore the initial moveto, and stop when the 1st contour ends
959void SkPath::pathTo(const SkPath& path) {
960 int i, vcount = path.fVerbs.count();
961 if (vcount == 0) {
962 return;
963 }
964
965 this->incReserve(vcount);
966
967 const uint8_t* verbs = path.fVerbs.begin();
968 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
969
970 SkASSERT(verbs[0] == kMove_Verb);
971 for (i = 1; i < vcount; i++) {
972 switch (verbs[i]) {
973 case kLine_Verb:
974 this->lineTo(pts[0].fX, pts[0].fY);
975 break;
976 case kQuad_Verb:
977 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
978 break;
979 case kCubic_Verb:
980 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
981 pts[2].fX, pts[2].fY);
982 break;
983 case kClose_Verb:
984 return;
985 }
986 pts += gPtsInVerb[verbs[i]];
987 }
988}
989
990// ignore the last point of the 1st contour
991void SkPath::reversePathTo(const SkPath& path) {
992 int i, vcount = path.fVerbs.count();
993 if (vcount == 0) {
994 return;
995 }
996
997 this->incReserve(vcount);
998
999 const uint8_t* verbs = path.fVerbs.begin();
1000 const SkPoint* pts = path.fPts.begin();
1001
1002 SkASSERT(verbs[0] == kMove_Verb);
1003 for (i = 1; i < vcount; i++) {
1004 int n = gPtsInVerb[verbs[i]];
1005 if (n == 0) {
1006 break;
1007 }
1008 pts += n;
1009 }
1010
1011 while (--i > 0) {
1012 switch (verbs[i]) {
1013 case kLine_Verb:
1014 this->lineTo(pts[-1].fX, pts[-1].fY);
1015 break;
1016 case kQuad_Verb:
1017 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1018 break;
1019 case kCubic_Verb:
1020 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1021 pts[-3].fX, pts[-3].fY);
1022 break;
1023 default:
1024 SkASSERT(!"bad verb");
1025 break;
1026 }
1027 pts -= gPtsInVerb[verbs[i]];
1028 }
1029}
1030
1031///////////////////////////////////////////////////////////////////////////////
1032
1033void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1034 SkMatrix matrix;
1035
1036 matrix.setTranslate(dx, dy);
1037 this->transform(matrix, dst);
1038}
1039
1040#include "SkGeometry.h"
1041
1042static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1043 int level = 2) {
1044 if (--level >= 0) {
1045 SkPoint tmp[5];
1046
1047 SkChopQuadAtHalf(pts, tmp);
1048 subdivide_quad_to(path, &tmp[0], level);
1049 subdivide_quad_to(path, &tmp[2], level);
1050 } else {
1051 path->quadTo(pts[1], pts[2]);
1052 }
1053}
1054
1055static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1056 int level = 2) {
1057 if (--level >= 0) {
1058 SkPoint tmp[7];
1059
1060 SkChopCubicAtHalf(pts, tmp);
1061 subdivide_cubic_to(path, &tmp[0], level);
1062 subdivide_cubic_to(path, &tmp[3], level);
1063 } else {
1064 path->cubicTo(pts[1], pts[2], pts[3]);
1065 }
1066}
1067
1068void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1069 SkDEBUGCODE(this->validate();)
1070 if (dst == NULL) {
1071 dst = (SkPath*)this;
1072 }
1073
tomhudson@google.com8d430182011-06-06 19:11:19 +00001074 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkPath tmp;
1076 tmp.fFillType = fFillType;
1077
1078 SkPath::Iter iter(*this, false);
1079 SkPoint pts[4];
1080 SkPath::Verb verb;
1081
1082 while ((verb = iter.next(pts)) != kDone_Verb) {
1083 switch (verb) {
1084 case kMove_Verb:
1085 tmp.moveTo(pts[0]);
1086 break;
1087 case kLine_Verb:
1088 tmp.lineTo(pts[1]);
1089 break;
1090 case kQuad_Verb:
1091 subdivide_quad_to(&tmp, pts);
1092 break;
1093 case kCubic_Verb:
1094 subdivide_cubic_to(&tmp, pts);
1095 break;
1096 case kClose_Verb:
1097 tmp.close();
1098 break;
1099 default:
1100 SkASSERT(!"unknown verb");
1101 break;
1102 }
1103 }
1104
1105 dst->swap(tmp);
1106 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1107 } else {
1108 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001109 // fBoundsIsDirty before we set it
1110 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001112 matrix.mapRect(&dst->fBounds, fBounds);
1113 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001115 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001116 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117 }
1118
1119 if (this != dst) {
1120 dst->fVerbs = fVerbs;
1121 dst->fPts.setCount(fPts.count());
1122 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001123 dst->fSegmentMask = fSegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 }
1125 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1126 SkDEBUGCODE(dst->validate();)
1127 }
1128}
1129
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130///////////////////////////////////////////////////////////////////////////////
1131///////////////////////////////////////////////////////////////////////////////
1132
1133enum NeedMoveToState {
1134 kAfterClose_NeedMoveToState,
1135 kAfterCons_NeedMoveToState,
1136 kAfterPrefix_NeedMoveToState
1137};
1138
1139SkPath::Iter::Iter() {
1140#ifdef SK_DEBUG
1141 fPts = NULL;
1142 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1143 fForceClose = fNeedMoveTo = fCloseLine = false;
1144#endif
1145 // need to init enough to make next() harmlessly return kDone_Verb
1146 fVerbs = NULL;
1147 fVerbStop = NULL;
1148 fNeedClose = false;
1149}
1150
1151SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1152 this->setPath(path, forceClose);
1153}
1154
1155void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1156 fPts = path.fPts.begin();
1157 fVerbs = path.fVerbs.begin();
1158 fVerbStop = path.fVerbs.end();
1159 fForceClose = SkToU8(forceClose);
1160 fNeedClose = false;
1161 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1162}
1163
1164bool SkPath::Iter::isClosedContour() const {
1165 if (fVerbs == NULL || fVerbs == fVerbStop) {
1166 return false;
1167 }
1168 if (fForceClose) {
1169 return true;
1170 }
1171
1172 const uint8_t* verbs = fVerbs;
1173 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001174
reed@android.com8a1c16f2008-12-17 15:59:43 +00001175 if (kMove_Verb == *verbs) {
1176 verbs += 1; // skip the initial moveto
1177 }
1178
1179 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001180 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 if (kMove_Verb == v) {
1182 break;
1183 }
1184 if (kClose_Verb == v) {
1185 return true;
1186 }
1187 }
1188 return false;
1189}
1190
1191SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1192 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001193 // A special case: if both points are NaN, SkPoint::operation== returns
1194 // false, but the iterator expects that they are treated as the same.
1195 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001196 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1197 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001198 return kClose_Verb;
1199 }
1200
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201 if (pts) {
1202 pts[0] = fLastPt;
1203 pts[1] = fMoveTo;
1204 }
1205 fLastPt = fMoveTo;
1206 fCloseLine = true;
1207 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001208 } else {
1209 pts[0] = fMoveTo;
1210 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212}
1213
1214bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1215 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1216 if (pts) {
1217 *pts = fMoveTo;
1218 }
1219 fNeedClose = fForceClose;
1220 fNeedMoveTo = kAfterCons_NeedMoveToState;
1221 fVerbs -= 1;
1222 return true;
1223 }
1224
1225 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1226 if (pts) {
1227 *pts = fMoveTo;
1228 }
1229 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1230 } else {
1231 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1232 if (pts) {
1233 *pts = fPts[-1];
1234 }
1235 }
1236 return false;
1237}
1238
1239SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1240 if (fVerbs == fVerbStop) {
1241 if (fNeedClose) {
1242 if (kLine_Verb == this->autoClose(pts)) {
1243 return kLine_Verb;
1244 }
1245 fNeedClose = false;
1246 return kClose_Verb;
1247 }
1248 return kDone_Verb;
1249 }
1250
1251 unsigned verb = *fVerbs++;
1252 const SkPoint* srcPts = fPts;
1253
1254 switch (verb) {
1255 case kMove_Verb:
1256 if (fNeedClose) {
1257 fVerbs -= 1;
1258 verb = this->autoClose(pts);
1259 if (verb == kClose_Verb) {
1260 fNeedClose = false;
1261 }
1262 return (Verb)verb;
1263 }
1264 if (fVerbs == fVerbStop) { // might be a trailing moveto
1265 return kDone_Verb;
1266 }
1267 fMoveTo = *srcPts;
1268 if (pts) {
1269 pts[0] = *srcPts;
1270 }
1271 srcPts += 1;
1272 fNeedMoveTo = kAfterCons_NeedMoveToState;
1273 fNeedClose = fForceClose;
1274 break;
1275 case kLine_Verb:
1276 if (this->cons_moveTo(pts)) {
1277 return kMove_Verb;
1278 }
1279 if (pts) {
1280 pts[1] = srcPts[0];
1281 }
1282 fLastPt = srcPts[0];
1283 fCloseLine = false;
1284 srcPts += 1;
1285 break;
1286 case kQuad_Verb:
1287 if (this->cons_moveTo(pts)) {
1288 return kMove_Verb;
1289 }
1290 if (pts) {
1291 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1292 }
1293 fLastPt = srcPts[1];
1294 srcPts += 2;
1295 break;
1296 case kCubic_Verb:
1297 if (this->cons_moveTo(pts)) {
1298 return kMove_Verb;
1299 }
1300 if (pts) {
1301 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1302 }
1303 fLastPt = srcPts[2];
1304 srcPts += 3;
1305 break;
1306 case kClose_Verb:
1307 verb = this->autoClose(pts);
1308 if (verb == kLine_Verb) {
1309 fVerbs -= 1;
1310 } else {
1311 fNeedClose = false;
1312 }
1313 fNeedMoveTo = kAfterClose_NeedMoveToState;
1314 break;
1315 }
1316 fPts = srcPts;
1317 return (Verb)verb;
1318}
1319
1320///////////////////////////////////////////////////////////////////////////////
1321
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322/*
1323 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1324*/
1325
reed@google.com73945652011-04-25 19:04:27 +00001326void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 SkDEBUGCODE(this->validate();)
1328
1329 buffer.write32(fPts.count());
1330 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001331 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1333 buffer.writePad(fVerbs.begin(), fVerbs.count());
1334}
1335
reed@google.com73945652011-04-25 19:04:27 +00001336void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 fPts.setCount(buffer.readS32());
1338 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001339 uint32_t packed = buffer.readS32();
1340 fFillType = packed >> 8;
1341 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1343 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001344
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001345 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001346 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347
1348 SkDEBUGCODE(this->validate();)
1349}
1350
1351///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352///////////////////////////////////////////////////////////////////////////////
1353
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354void SkPath::dump(bool forceClose, const char title[]) const {
1355 Iter iter(*this, forceClose);
1356 SkPoint pts[4];
1357 Verb verb;
1358
1359 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1360 title ? title : "");
1361
1362 while ((verb = iter.next(pts)) != kDone_Verb) {
1363 switch (verb) {
1364 case kMove_Verb:
1365#ifdef SK_CAN_USE_FLOAT
1366 SkDebugf(" path: moveTo [%g %g]\n",
1367 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1368#else
1369 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1370#endif
1371 break;
1372 case kLine_Verb:
1373#ifdef SK_CAN_USE_FLOAT
1374 SkDebugf(" path: lineTo [%g %g]\n",
1375 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1376#else
1377 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1378#endif
1379 break;
1380 case kQuad_Verb:
1381#ifdef SK_CAN_USE_FLOAT
1382 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1383 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1384 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1385#else
1386 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1387 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1388#endif
1389 break;
1390 case kCubic_Verb:
1391#ifdef SK_CAN_USE_FLOAT
1392 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1393 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1394 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1395 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1396#else
1397 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1398 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1399 pts[3].fX, pts[3].fY);
1400#endif
1401 break;
1402 case kClose_Verb:
1403 SkDebugf(" path: close\n");
1404 break;
1405 default:
1406 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1407 verb = kDone_Verb; // stop the loop
1408 break;
1409 }
1410 }
1411 SkDebugf("path: done %s\n", title ? title : "");
1412}
1413
reed@android.come522ca52009-11-23 20:10:41 +00001414void SkPath::dump() const {
1415 this->dump(false);
1416}
1417
1418#ifdef SK_DEBUG
1419void SkPath::validate() const {
1420 SkASSERT(this != NULL);
1421 SkASSERT((fFillType & ~3) == 0);
1422 fPts.validate();
1423 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001424
reed@android.come522ca52009-11-23 20:10:41 +00001425 if (!fBoundsIsDirty) {
1426 SkRect bounds;
1427 compute_pt_bounds(&bounds, fPts);
1428 if (fPts.count() <= 1) {
1429 // if we're empty, fBounds may be empty but translated, so we can't
1430 // necessarily compare to bounds directly
1431 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1432 // be [2, 2, 2, 2]
1433 SkASSERT(bounds.isEmpty());
1434 SkASSERT(fBounds.isEmpty());
1435 } else {
1436 fBounds.contains(bounds);
1437 }
1438 }
reed@google.com10296cc2011-09-21 12:29:05 +00001439
1440 uint32_t mask = 0;
1441 for (int i = 0; i < fVerbs.count(); i++) {
1442 switch (fVerbs[i]) {
1443 case kLine_Verb:
1444 mask |= kLine_SegmentMask;
1445 break;
1446 case kQuad_Verb:
1447 mask |= kQuad_SegmentMask;
1448 break;
1449 case kCubic_Verb:
1450 mask |= kCubic_SegmentMask;
1451 }
1452 }
1453 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001454}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455#endif
reed@android.come522ca52009-11-23 20:10:41 +00001456
reed@google.com04863fa2011-05-15 04:08:24 +00001457///////////////////////////////////////////////////////////////////////////////
1458
reed@google.com0b7b9822011-05-16 12:29:27 +00001459static int sign(SkScalar x) { return x < 0; }
1460#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001461
reed@google.com04863fa2011-05-15 04:08:24 +00001462static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001463 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001464}
1465
1466// only valid for a single contour
1467struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001468 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001469 fSign = 0;
1470 // warnings
1471 fCurrPt.set(0, 0);
1472 fVec0.set(0, 0);
1473 fVec1.set(0, 0);
1474 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001475
1476 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001477 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001478 }
1479
1480 SkPath::Convexity getConvexity() const { return fConvexity; }
1481
1482 void addPt(const SkPoint& pt) {
1483 if (SkPath::kConcave_Convexity == fConvexity) {
1484 return;
1485 }
1486
1487 if (0 == fPtCount) {
1488 fCurrPt = pt;
1489 ++fPtCount;
1490 } else {
1491 SkVector vec = pt - fCurrPt;
1492 if (vec.fX || vec.fY) {
1493 fCurrPt = pt;
1494 if (++fPtCount == 2) {
1495 fFirstVec = fVec1 = vec;
1496 } else {
1497 SkASSERT(fPtCount > 2);
1498 this->addVec(vec);
1499 }
reed@google.com85b6e392011-05-15 20:25:17 +00001500
1501 int sx = sign(vec.fX);
1502 int sy = sign(vec.fY);
1503 fDx += (sx != fSx);
1504 fDy += (sy != fSy);
1505 fSx = sx;
1506 fSy = sy;
1507
1508 if (fDx > 3 || fDy > 3) {
1509 fConvexity = SkPath::kConcave_Convexity;
1510 }
reed@google.com04863fa2011-05-15 04:08:24 +00001511 }
1512 }
1513 }
1514
1515 void close() {
1516 if (fPtCount > 2) {
1517 this->addVec(fFirstVec);
1518 }
1519 }
1520
1521private:
1522 void addVec(const SkVector& vec) {
1523 SkASSERT(vec.fX || vec.fY);
1524 fVec0 = fVec1;
1525 fVec1 = vec;
1526 int sign = CrossProductSign(fVec0, fVec1);
1527 if (0 == fSign) {
1528 fSign = sign;
1529 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001530 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001531 fConvexity = SkPath::kConcave_Convexity;
1532 }
1533 }
1534 }
1535
1536 SkPoint fCurrPt;
1537 SkVector fVec0, fVec1, fFirstVec;
1538 int fPtCount; // non-degenerate points
1539 int fSign;
1540 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001541 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001542};
1543
1544SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1545 SkPoint pts[4];
1546 SkPath::Verb verb;
1547 SkPath::Iter iter(path, true);
1548
1549 int contourCount = 0;
1550 int count;
1551 Convexicator state;
1552
1553 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1554 switch (verb) {
1555 case kMove_Verb:
1556 if (++contourCount > 1) {
1557 return kConcave_Convexity;
1558 }
1559 pts[1] = pts[0];
1560 count = 1;
1561 break;
1562 case kLine_Verb: count = 1; break;
1563 case kQuad_Verb: count = 2; break;
1564 case kCubic_Verb: count = 3; break;
1565 case kClose_Verb:
1566 state.close();
1567 count = 0;
1568 break;
1569 default:
1570 SkASSERT(!"bad verb");
1571 return kConcave_Convexity;
1572 }
1573
1574 for (int i = 1; i <= count; i++) {
1575 state.addPt(pts[i]);
1576 }
1577 // early exit
1578 if (kConcave_Convexity == state.getConvexity()) {
1579 return kConcave_Convexity;
1580 }
1581 }
1582 return state.getConvexity();
1583}