blob: 72560218119b4031f20e084df7407846f9c7d781 [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:
92 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
93 - 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
96 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
97 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
98 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
99*/
100
101////////////////////////////////////////////////////////////////////////////
102
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000103SkPath::SkPath()
104 : fFillType(kWinding_FillType)
105 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000106 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000107 fSegmentMask = 0;
djsollen@google.com56c69772011-11-08 19:00:26 +0000108#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000109 fGenerationID = 0;
110#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000111}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112
113SkPath::SkPath(const SkPath& src) {
114 SkDEBUGCODE(src.validate();)
115 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000116#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000117 // the assignment operator above increments the ID so correct for that here
118 fGenerationID--;
119#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120}
121
122SkPath::~SkPath() {
123 SkDEBUGCODE(this->validate();)
124}
125
126SkPath& SkPath::operator=(const SkPath& src) {
127 SkDEBUGCODE(src.validate();)
128
129 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000130 fBounds = src.fBounds;
131 fPts = src.fPts;
132 fVerbs = src.fVerbs;
133 fFillType = src.fFillType;
134 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000135 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000136 fSegmentMask = src.fSegmentMask;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000137 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 }
139 SkDEBUGCODE(this->validate();)
140 return *this;
141}
142
reed@android.com3abec1d2009-03-02 05:36:20 +0000143bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000144 // note: don't need to look at isConvex or bounds, since just comparing the
145 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000146
147 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
148 // since it is only a cache of info in the fVerbs, but its a fast way to
149 // notice a difference
150
reed@android.com3abec1d2009-03-02 05:36:20 +0000151 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000152 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
153 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000154}
155
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156void SkPath::swap(SkPath& other) {
157 SkASSERT(&other != NULL);
158
159 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000160 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 fPts.swap(other.fPts);
162 fVerbs.swap(other.fVerbs);
163 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000164 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000165 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000166 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000167 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168 }
169}
170
djsollen@google.com56c69772011-11-08 19:00:26 +0000171#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000172uint32_t SkPath::getGenerationID() const {
173 return fGenerationID;
174}
175#endif
176
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177void SkPath::reset() {
178 SkDEBUGCODE(this->validate();)
179
180 fPts.reset();
181 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000182 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000183 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000184 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000185 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186}
187
188void SkPath::rewind() {
189 SkDEBUGCODE(this->validate();)
190
191 fPts.rewind();
192 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000193 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000194 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000195 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000196 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197}
198
199bool SkPath::isEmpty() const {
200 SkDEBUGCODE(this->validate();)
201
202 int count = fVerbs.count();
203 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
204}
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
reed@google.comb54455e2011-05-16 14:16:04 +0000388#define DIRTY_AFTER_EDIT \
389 do { \
390 fBoundsIsDirty = true; \
391 fConvexity = kUnknown_Convexity;\
392 } 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
409 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
410 pt = &fPts[fPts.count() - 1];
411 } else {
412 pt = fPts.append();
413 *fVerbs.append() = kMove_Verb;
414 }
415 pt->set(x, y);
416
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000417 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000418 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419}
420
421void SkPath::rMoveTo(SkScalar x, SkScalar y) {
422 SkPoint pt;
423 this->getLastPt(&pt);
424 this->moveTo(pt.fX + x, pt.fY + y);
425}
426
427void SkPath::lineTo(SkScalar x, SkScalar y) {
428 SkDEBUGCODE(this->validate();)
429
430 if (fVerbs.count() == 0) {
431 fPts.append()->set(0, 0);
432 *fVerbs.append() = kMove_Verb;
433 }
434 fPts.append()->set(x, y);
435 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000436 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000438 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000439 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000440}
441
442void SkPath::rLineTo(SkScalar x, SkScalar y) {
443 SkPoint pt;
444 this->getLastPt(&pt);
445 this->lineTo(pt.fX + x, pt.fY + y);
446}
447
448void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
449 SkDEBUGCODE(this->validate();)
450
451 if (fVerbs.count() == 0) {
452 fPts.append()->set(0, 0);
453 *fVerbs.append() = kMove_Verb;
454 }
455
456 SkPoint* pts = fPts.append(2);
457 pts[0].set(x1, y1);
458 pts[1].set(x2, y2);
459 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000460 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000462 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000463 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464}
465
466void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
467 SkPoint pt;
468 this->getLastPt(&pt);
469 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
470}
471
472void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
473 SkScalar x3, SkScalar y3) {
474 SkDEBUGCODE(this->validate();)
475
476 if (fVerbs.count() == 0) {
477 fPts.append()->set(0, 0);
478 *fVerbs.append() = kMove_Verb;
479 }
480 SkPoint* pts = fPts.append(3);
481 pts[0].set(x1, y1);
482 pts[1].set(x2, y2);
483 pts[2].set(x3, y3);
484 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000485 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000486
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000487 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000488 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489}
490
491void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
492 SkScalar x3, SkScalar y3) {
493 SkPoint pt;
494 this->getLastPt(&pt);
495 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
496 pt.fX + x3, pt.fY + y3);
497}
498
499void SkPath::close() {
500 SkDEBUGCODE(this->validate();)
501
502 int count = fVerbs.count();
503 if (count > 0) {
504 switch (fVerbs[count - 1]) {
505 case kLine_Verb:
506 case kQuad_Verb:
507 case kCubic_Verb:
508 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000509 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000510 break;
511 default:
512 // don't add a close if the prev wasn't a primitive
513 break;
514 }
515 }
516}
517
518///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000519
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520void SkPath::addRect(const SkRect& rect, Direction dir) {
521 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
522}
523
524void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
525 SkScalar bottom, Direction dir) {
526 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
527
528 this->incReserve(5);
529
530 this->moveTo(left, top);
531 if (dir == kCCW_Direction) {
532 this->lineTo(left, bottom);
533 this->lineTo(right, bottom);
534 this->lineTo(right, top);
535 } else {
536 this->lineTo(right, top);
537 this->lineTo(right, bottom);
538 this->lineTo(left, bottom);
539 }
540 this->close();
541}
542
543#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
544
545void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
546 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547 SkScalar w = rect.width();
548 SkScalar halfW = SkScalarHalf(w);
549 SkScalar h = rect.height();
550 SkScalar halfH = SkScalarHalf(h);
551
552 if (halfW <= 0 || halfH <= 0) {
553 return;
554 }
555
reed@google.comabf15c12011-01-18 20:35:51 +0000556 bool skip_hori = rx >= halfW;
557 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000558
559 if (skip_hori && skip_vert) {
560 this->addOval(rect, dir);
561 return;
562 }
reed@google.comabf15c12011-01-18 20:35:51 +0000563
564 SkAutoPathBoundsUpdate apbu(this, rect);
565
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566 if (skip_hori) {
567 rx = halfW;
568 } else if (skip_vert) {
569 ry = halfH;
570 }
571
572 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
573 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
574
575 this->incReserve(17);
576 this->moveTo(rect.fRight - rx, rect.fTop);
577 if (dir == kCCW_Direction) {
578 if (!skip_hori) {
579 this->lineTo(rect.fLeft + rx, rect.fTop); // top
580 }
581 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
582 rect.fLeft, rect.fTop + ry - sy,
583 rect.fLeft, rect.fTop + ry); // top-left
584 if (!skip_vert) {
585 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
586 }
587 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
588 rect.fLeft + rx - sx, rect.fBottom,
589 rect.fLeft + rx, rect.fBottom); // bot-left
590 if (!skip_hori) {
591 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
592 }
593 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
594 rect.fRight, rect.fBottom - ry + sy,
595 rect.fRight, rect.fBottom - ry); // bot-right
596 if (!skip_vert) {
597 this->lineTo(rect.fRight, rect.fTop + ry);
598 }
599 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
600 rect.fRight - rx + sx, rect.fTop,
601 rect.fRight - rx, rect.fTop); // top-right
602 } else {
603 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
604 rect.fRight, rect.fTop + ry - sy,
605 rect.fRight, rect.fTop + ry); // top-right
606 if (!skip_vert) {
607 this->lineTo(rect.fRight, rect.fBottom - ry);
608 }
609 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
610 rect.fRight - rx + sx, rect.fBottom,
611 rect.fRight - rx, rect.fBottom); // bot-right
612 if (!skip_hori) {
613 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
614 }
615 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
616 rect.fLeft, rect.fBottom - ry + sy,
617 rect.fLeft, rect.fBottom - ry); // bot-left
618 if (!skip_vert) {
619 this->lineTo(rect.fLeft, rect.fTop + ry); // left
620 }
621 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
622 rect.fLeft + rx - sx, rect.fTop,
623 rect.fLeft + rx, rect.fTop); // top-left
624 if (!skip_hori) {
625 this->lineTo(rect.fRight - rx, rect.fTop); // top
626 }
627 }
628 this->close();
629}
630
631static void add_corner_arc(SkPath* path, const SkRect& rect,
632 SkScalar rx, SkScalar ry, int startAngle,
633 SkPath::Direction dir, bool forceMoveTo) {
634 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
635 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000636
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 SkRect r;
638 r.set(-rx, -ry, rx, ry);
639
640 switch (startAngle) {
641 case 0:
642 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
643 break;
644 case 90:
645 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
646 break;
647 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
648 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
649 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
650 }
reed@google.comabf15c12011-01-18 20:35:51 +0000651
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 SkScalar start = SkIntToScalar(startAngle);
653 SkScalar sweep = SkIntToScalar(90);
654 if (SkPath::kCCW_Direction == dir) {
655 start += sweep;
656 sweep = -sweep;
657 }
reed@google.comabf15c12011-01-18 20:35:51 +0000658
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659 path->arcTo(r, start, sweep, forceMoveTo);
660}
661
662void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
663 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000664 // abort before we invoke SkAutoPathBoundsUpdate()
665 if (rect.isEmpty()) {
666 return;
667 }
668
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 SkAutoPathBoundsUpdate apbu(this, rect);
670
671 if (kCW_Direction == dir) {
672 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
673 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
674 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
675 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
676 } else {
677 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
678 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
679 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
680 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
681 }
682 this->close();
683}
684
685void SkPath::addOval(const SkRect& oval, Direction dir) {
686 SkAutoPathBoundsUpdate apbu(this, oval);
687
688 SkScalar cx = oval.centerX();
689 SkScalar cy = oval.centerY();
690 SkScalar rx = SkScalarHalf(oval.width());
691 SkScalar ry = SkScalarHalf(oval.height());
692#if 0 // these seem faster than using quads (1/2 the number of edges)
693 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
694 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
695
696 this->incReserve(13);
697 this->moveTo(cx + rx, cy);
698 if (dir == kCCW_Direction) {
699 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
700 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
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 } else {
704 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
705 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
706 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
707 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
708 }
709#else
710 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
711 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
712 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
713 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
714
715 /*
716 To handle imprecision in computing the center and radii, we revert to
717 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
718 to ensure that we don't exceed the oval's bounds *ever*, since we want
719 to use oval for our fast-bounds, rather than have to recompute it.
720 */
721 const SkScalar L = oval.fLeft; // cx - rx
722 const SkScalar T = oval.fTop; // cy - ry
723 const SkScalar R = oval.fRight; // cx + rx
724 const SkScalar B = oval.fBottom; // cy + ry
725
726 this->incReserve(17); // 8 quads + close
727 this->moveTo(R, cy);
728 if (dir == kCCW_Direction) {
729 this->quadTo( R, cy - sy, cx + mx, cy - my);
730 this->quadTo(cx + sx, T, cx , T);
731 this->quadTo(cx - sx, T, cx - mx, cy - my);
732 this->quadTo( L, cy - sy, L, cy );
733 this->quadTo( L, cy + sy, cx - mx, cy + my);
734 this->quadTo(cx - sx, B, cx , B);
735 this->quadTo(cx + sx, B, cx + mx, cy + my);
736 this->quadTo( R, cy + sy, R, cy );
737 } else {
738 this->quadTo( R, cy + sy, cx + mx, cy + my);
739 this->quadTo(cx + sx, B, cx , B);
740 this->quadTo(cx - sx, B, cx - mx, cy + my);
741 this->quadTo( L, cy + sy, L, cy );
742 this->quadTo( L, cy - sy, cx - mx, cy - my);
743 this->quadTo(cx - sx, T, cx , T);
744 this->quadTo(cx + sx, T, cx + mx, cy - my);
745 this->quadTo( R, cy - sy, R, cy );
746 }
747#endif
748 this->close();
749}
750
751void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
752 if (r > 0) {
753 SkRect rect;
754 rect.set(x - r, y - r, x + r, y + r);
755 this->addOval(rect, dir);
756 }
757}
758
759#include "SkGeometry.h"
760
761static int build_arc_points(const SkRect& oval, SkScalar startAngle,
762 SkScalar sweepAngle,
763 SkPoint pts[kSkBuildQuadArcStorage]) {
764 SkVector start, stop;
765
766 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
767 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
768 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000769
770 /* If the sweep angle is nearly (but less than) 360, then due to precision
771 loss in radians-conversion and/or sin/cos, we may end up with coincident
772 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
773 of drawing a nearly complete circle (good).
774 e.g. canvas.drawArc(0, 359.99, ...)
775 -vs- canvas.drawArc(0, 359.9, ...)
776 We try to detect this edge case, and tweak the stop vector
777 */
778 if (start == stop) {
779 SkScalar sw = SkScalarAbs(sweepAngle);
780 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
781 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
782 // make a guess at a tiny angle (in radians) to tweak by
783 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
784 // not sure how much will be enough, so we use a loop
785 do {
786 stopRad -= deltaRad;
787 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
788 } while (start == stop);
789 }
790 }
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000793
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
795 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000796
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 return SkBuildQuadArc(start, stop,
798 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
799 &matrix, pts);
800}
801
802void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
803 bool forceMoveTo) {
804 if (oval.width() < 0 || oval.height() < 0) {
805 return;
806 }
807
808 SkPoint pts[kSkBuildQuadArcStorage];
809 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
810 SkASSERT((count & 1) == 1);
811
812 if (fVerbs.count() == 0) {
813 forceMoveTo = true;
814 }
815 this->incReserve(count);
816 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
817 for (int i = 1; i < count; i += 2) {
818 this->quadTo(pts[i], pts[i+1]);
819 }
820}
821
822void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
823 SkScalar sweepAngle) {
824 if (oval.isEmpty() || 0 == sweepAngle) {
825 return;
826 }
827
828 const SkScalar kFullCircleAngle = SkIntToScalar(360);
829
830 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
831 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
832 return;
833 }
834
835 SkPoint pts[kSkBuildQuadArcStorage];
836 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
837
838 this->incReserve(count);
839 this->moveTo(pts[0]);
840 for (int i = 1; i < count; i += 2) {
841 this->quadTo(pts[i], pts[i+1]);
842 }
843}
844
845/*
846 Need to handle the case when the angle is sharp, and our computed end-points
847 for the arc go behind pt1 and/or p2...
848*/
849void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
850 SkScalar radius) {
851 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000852
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 // need to know our prev pt so we can construct tangent vectors
854 {
855 SkPoint start;
856 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000857 // Handle degenerate cases by adding a line to the first point and
858 // bailing out.
859 if ((x1 == start.fX && y1 == start.fY) ||
860 (x1 == x2 && y1 == y2) ||
861 radius == 0) {
862 this->lineTo(x1, y1);
863 return;
864 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 before.setNormalize(x1 - start.fX, y1 - start.fY);
866 after.setNormalize(x2 - x1, y2 - y1);
867 }
reed@google.comabf15c12011-01-18 20:35:51 +0000868
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 SkScalar cosh = SkPoint::DotProduct(before, after);
870 SkScalar sinh = SkPoint::CrossProduct(before, after);
871
872 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000873 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 return;
875 }
reed@google.comabf15c12011-01-18 20:35:51 +0000876
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
878 if (dist < 0) {
879 dist = -dist;
880 }
881
882 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
883 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
884 SkRotationDirection arcDir;
885
886 // now turn before/after into normals
887 if (sinh > 0) {
888 before.rotateCCW();
889 after.rotateCCW();
890 arcDir = kCW_SkRotationDirection;
891 } else {
892 before.rotateCW();
893 after.rotateCW();
894 arcDir = kCCW_SkRotationDirection;
895 }
896
897 SkMatrix matrix;
898 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000899
reed@android.com8a1c16f2008-12-17 15:59:43 +0000900 matrix.setScale(radius, radius);
901 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
902 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000903
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000905
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 this->incReserve(count);
907 // [xx,yy] == pts[0]
908 this->lineTo(xx, yy);
909 for (int i = 1; i < count; i += 2) {
910 this->quadTo(pts[i], pts[i+1]);
911 }
912}
913
914///////////////////////////////////////////////////////////////////////////////
915
916void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
917 SkMatrix matrix;
918
919 matrix.setTranslate(dx, dy);
920 this->addPath(path, matrix);
921}
922
923void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
924 this->incReserve(path.fPts.count());
925
926 Iter iter(path, false);
927 SkPoint pts[4];
928 Verb verb;
929
930 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
931
932 while ((verb = iter.next(pts)) != kDone_Verb) {
933 switch (verb) {
934 case kMove_Verb:
935 proc(matrix, &pts[0], &pts[0], 1);
936 this->moveTo(pts[0]);
937 break;
938 case kLine_Verb:
939 proc(matrix, &pts[1], &pts[1], 1);
940 this->lineTo(pts[1]);
941 break;
942 case kQuad_Verb:
943 proc(matrix, &pts[1], &pts[1], 2);
944 this->quadTo(pts[1], pts[2]);
945 break;
946 case kCubic_Verb:
947 proc(matrix, &pts[1], &pts[1], 3);
948 this->cubicTo(pts[1], pts[2], pts[3]);
949 break;
950 case kClose_Verb:
951 this->close();
952 break;
953 default:
954 SkASSERT(!"unknown verb");
955 }
956 }
957}
958
959///////////////////////////////////////////////////////////////////////////////
960
961static const uint8_t gPtsInVerb[] = {
962 1, // kMove
963 1, // kLine
964 2, // kQuad
965 3, // kCubic
966 0, // kClose
967 0 // kDone
968};
969
970// ignore the initial moveto, and stop when the 1st contour ends
971void SkPath::pathTo(const SkPath& path) {
972 int i, vcount = path.fVerbs.count();
973 if (vcount == 0) {
974 return;
975 }
976
977 this->incReserve(vcount);
978
979 const uint8_t* verbs = path.fVerbs.begin();
980 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
981
982 SkASSERT(verbs[0] == kMove_Verb);
983 for (i = 1; i < vcount; i++) {
984 switch (verbs[i]) {
985 case kLine_Verb:
986 this->lineTo(pts[0].fX, pts[0].fY);
987 break;
988 case kQuad_Verb:
989 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
990 break;
991 case kCubic_Verb:
992 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
993 pts[2].fX, pts[2].fY);
994 break;
995 case kClose_Verb:
996 return;
997 }
998 pts += gPtsInVerb[verbs[i]];
999 }
1000}
1001
1002// ignore the last point of the 1st contour
1003void SkPath::reversePathTo(const SkPath& path) {
1004 int i, vcount = path.fVerbs.count();
1005 if (vcount == 0) {
1006 return;
1007 }
1008
1009 this->incReserve(vcount);
1010
1011 const uint8_t* verbs = path.fVerbs.begin();
1012 const SkPoint* pts = path.fPts.begin();
1013
1014 SkASSERT(verbs[0] == kMove_Verb);
1015 for (i = 1; i < vcount; i++) {
1016 int n = gPtsInVerb[verbs[i]];
1017 if (n == 0) {
1018 break;
1019 }
1020 pts += n;
1021 }
1022
1023 while (--i > 0) {
1024 switch (verbs[i]) {
1025 case kLine_Verb:
1026 this->lineTo(pts[-1].fX, pts[-1].fY);
1027 break;
1028 case kQuad_Verb:
1029 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1030 break;
1031 case kCubic_Verb:
1032 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1033 pts[-3].fX, pts[-3].fY);
1034 break;
1035 default:
1036 SkASSERT(!"bad verb");
1037 break;
1038 }
1039 pts -= gPtsInVerb[verbs[i]];
1040 }
1041}
1042
1043///////////////////////////////////////////////////////////////////////////////
1044
1045void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1046 SkMatrix matrix;
1047
1048 matrix.setTranslate(dx, dy);
1049 this->transform(matrix, dst);
1050}
1051
1052#include "SkGeometry.h"
1053
1054static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1055 int level = 2) {
1056 if (--level >= 0) {
1057 SkPoint tmp[5];
1058
1059 SkChopQuadAtHalf(pts, tmp);
1060 subdivide_quad_to(path, &tmp[0], level);
1061 subdivide_quad_to(path, &tmp[2], level);
1062 } else {
1063 path->quadTo(pts[1], pts[2]);
1064 }
1065}
1066
1067static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1068 int level = 2) {
1069 if (--level >= 0) {
1070 SkPoint tmp[7];
1071
1072 SkChopCubicAtHalf(pts, tmp);
1073 subdivide_cubic_to(path, &tmp[0], level);
1074 subdivide_cubic_to(path, &tmp[3], level);
1075 } else {
1076 path->cubicTo(pts[1], pts[2], pts[3]);
1077 }
1078}
1079
1080void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1081 SkDEBUGCODE(this->validate();)
1082 if (dst == NULL) {
1083 dst = (SkPath*)this;
1084 }
1085
tomhudson@google.com8d430182011-06-06 19:11:19 +00001086 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 SkPath tmp;
1088 tmp.fFillType = fFillType;
1089
1090 SkPath::Iter iter(*this, false);
1091 SkPoint pts[4];
1092 SkPath::Verb verb;
1093
1094 while ((verb = iter.next(pts)) != kDone_Verb) {
1095 switch (verb) {
1096 case kMove_Verb:
1097 tmp.moveTo(pts[0]);
1098 break;
1099 case kLine_Verb:
1100 tmp.lineTo(pts[1]);
1101 break;
1102 case kQuad_Verb:
1103 subdivide_quad_to(&tmp, pts);
1104 break;
1105 case kCubic_Verb:
1106 subdivide_cubic_to(&tmp, pts);
1107 break;
1108 case kClose_Verb:
1109 tmp.close();
1110 break;
1111 default:
1112 SkASSERT(!"unknown verb");
1113 break;
1114 }
1115 }
1116
1117 dst->swap(tmp);
1118 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1119 } else {
1120 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001121 // fBoundsIsDirty before we set it
1122 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001124 matrix.mapRect(&dst->fBounds, fBounds);
1125 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001127 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001128 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129 }
1130
1131 if (this != dst) {
1132 dst->fVerbs = fVerbs;
1133 dst->fPts.setCount(fPts.count());
1134 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001135 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001136 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 }
1138 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1139 SkDEBUGCODE(dst->validate();)
1140 }
1141}
1142
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143///////////////////////////////////////////////////////////////////////////////
1144///////////////////////////////////////////////////////////////////////////////
1145
1146enum NeedMoveToState {
1147 kAfterClose_NeedMoveToState,
1148 kAfterCons_NeedMoveToState,
1149 kAfterPrefix_NeedMoveToState
1150};
1151
1152SkPath::Iter::Iter() {
1153#ifdef SK_DEBUG
1154 fPts = NULL;
1155 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1156 fForceClose = fNeedMoveTo = fCloseLine = false;
1157#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();
1172 fForceClose = SkToU8(forceClose);
1173 fNeedClose = false;
1174 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1175}
1176
1177bool SkPath::Iter::isClosedContour() const {
1178 if (fVerbs == NULL || fVerbs == fVerbStop) {
1179 return false;
1180 }
1181 if (fForceClose) {
1182 return true;
1183 }
1184
1185 const uint8_t* verbs = fVerbs;
1186 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001187
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 if (kMove_Verb == *verbs) {
1189 verbs += 1; // skip the initial moveto
1190 }
1191
1192 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001193 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 if (kMove_Verb == v) {
1195 break;
1196 }
1197 if (kClose_Verb == v) {
1198 return true;
1199 }
1200 }
1201 return false;
1202}
1203
1204SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1205 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001206 // A special case: if both points are NaN, SkPoint::operation== returns
1207 // false, but the iterator expects that they are treated as the same.
1208 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001209 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1210 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001211 return kClose_Verb;
1212 }
1213
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 if (pts) {
1215 pts[0] = fLastPt;
1216 pts[1] = fMoveTo;
1217 }
1218 fLastPt = fMoveTo;
1219 fCloseLine = true;
1220 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001221 } else {
1222 pts[0] = fMoveTo;
1223 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225}
1226
1227bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1228 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1229 if (pts) {
1230 *pts = fMoveTo;
1231 }
1232 fNeedClose = fForceClose;
1233 fNeedMoveTo = kAfterCons_NeedMoveToState;
1234 fVerbs -= 1;
1235 return true;
1236 }
1237
1238 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1239 if (pts) {
1240 *pts = fMoveTo;
1241 }
1242 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1243 } else {
1244 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1245 if (pts) {
1246 *pts = fPts[-1];
1247 }
1248 }
1249 return false;
1250}
1251
1252SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1253 if (fVerbs == fVerbStop) {
1254 if (fNeedClose) {
1255 if (kLine_Verb == this->autoClose(pts)) {
1256 return kLine_Verb;
1257 }
1258 fNeedClose = false;
1259 return kClose_Verb;
1260 }
1261 return kDone_Verb;
1262 }
1263
1264 unsigned verb = *fVerbs++;
1265 const SkPoint* srcPts = fPts;
1266
1267 switch (verb) {
1268 case kMove_Verb:
1269 if (fNeedClose) {
1270 fVerbs -= 1;
1271 verb = this->autoClose(pts);
1272 if (verb == kClose_Verb) {
1273 fNeedClose = false;
1274 }
1275 return (Verb)verb;
1276 }
1277 if (fVerbs == fVerbStop) { // might be a trailing moveto
1278 return kDone_Verb;
1279 }
1280 fMoveTo = *srcPts;
1281 if (pts) {
1282 pts[0] = *srcPts;
1283 }
1284 srcPts += 1;
1285 fNeedMoveTo = kAfterCons_NeedMoveToState;
1286 fNeedClose = fForceClose;
1287 break;
1288 case kLine_Verb:
1289 if (this->cons_moveTo(pts)) {
1290 return kMove_Verb;
1291 }
1292 if (pts) {
1293 pts[1] = srcPts[0];
1294 }
1295 fLastPt = srcPts[0];
1296 fCloseLine = false;
1297 srcPts += 1;
1298 break;
1299 case kQuad_Verb:
1300 if (this->cons_moveTo(pts)) {
1301 return kMove_Verb;
1302 }
1303 if (pts) {
1304 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1305 }
1306 fLastPt = srcPts[1];
1307 srcPts += 2;
1308 break;
1309 case kCubic_Verb:
1310 if (this->cons_moveTo(pts)) {
1311 return kMove_Verb;
1312 }
1313 if (pts) {
1314 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1315 }
1316 fLastPt = srcPts[2];
1317 srcPts += 3;
1318 break;
1319 case kClose_Verb:
1320 verb = this->autoClose(pts);
1321 if (verb == kLine_Verb) {
1322 fVerbs -= 1;
1323 } else {
1324 fNeedClose = false;
1325 }
1326 fNeedMoveTo = kAfterClose_NeedMoveToState;
1327 break;
1328 }
1329 fPts = srcPts;
1330 return (Verb)verb;
1331}
1332
1333///////////////////////////////////////////////////////////////////////////////
1334
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335/*
1336 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1337*/
1338
reed@google.com73945652011-04-25 19:04:27 +00001339void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 SkDEBUGCODE(this->validate();)
1341
1342 buffer.write32(fPts.count());
1343 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001344 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1346 buffer.writePad(fVerbs.begin(), fVerbs.count());
1347}
1348
reed@google.com73945652011-04-25 19:04:27 +00001349void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 fPts.setCount(buffer.readS32());
1351 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001352 uint32_t packed = buffer.readS32();
1353 fFillType = packed >> 8;
1354 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1356 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001357
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001358 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001359 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360
1361 SkDEBUGCODE(this->validate();)
1362}
1363
1364///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365///////////////////////////////////////////////////////////////////////////////
1366
reed@android.com8a1c16f2008-12-17 15:59:43 +00001367void SkPath::dump(bool forceClose, const char title[]) const {
1368 Iter iter(*this, forceClose);
1369 SkPoint pts[4];
1370 Verb verb;
1371
1372 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1373 title ? title : "");
1374
1375 while ((verb = iter.next(pts)) != kDone_Verb) {
1376 switch (verb) {
1377 case kMove_Verb:
1378#ifdef SK_CAN_USE_FLOAT
1379 SkDebugf(" path: moveTo [%g %g]\n",
1380 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1381#else
1382 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1383#endif
1384 break;
1385 case kLine_Verb:
1386#ifdef SK_CAN_USE_FLOAT
1387 SkDebugf(" path: lineTo [%g %g]\n",
1388 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1389#else
1390 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1391#endif
1392 break;
1393 case kQuad_Verb:
1394#ifdef SK_CAN_USE_FLOAT
1395 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1396 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1397 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1398#else
1399 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1400 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1401#endif
1402 break;
1403 case kCubic_Verb:
1404#ifdef SK_CAN_USE_FLOAT
1405 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1406 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1407 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1408 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1409#else
1410 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1411 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1412 pts[3].fX, pts[3].fY);
1413#endif
1414 break;
1415 case kClose_Verb:
1416 SkDebugf(" path: close\n");
1417 break;
1418 default:
1419 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1420 verb = kDone_Verb; // stop the loop
1421 break;
1422 }
1423 }
1424 SkDebugf("path: done %s\n", title ? title : "");
1425}
1426
reed@android.come522ca52009-11-23 20:10:41 +00001427void SkPath::dump() const {
1428 this->dump(false);
1429}
1430
1431#ifdef SK_DEBUG
1432void SkPath::validate() const {
1433 SkASSERT(this != NULL);
1434 SkASSERT((fFillType & ~3) == 0);
1435 fPts.validate();
1436 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001437
reed@android.come522ca52009-11-23 20:10:41 +00001438 if (!fBoundsIsDirty) {
1439 SkRect bounds;
1440 compute_pt_bounds(&bounds, fPts);
1441 if (fPts.count() <= 1) {
1442 // if we're empty, fBounds may be empty but translated, so we can't
1443 // necessarily compare to bounds directly
1444 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1445 // be [2, 2, 2, 2]
1446 SkASSERT(bounds.isEmpty());
1447 SkASSERT(fBounds.isEmpty());
1448 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001449 if (bounds.isEmpty()) {
1450 SkASSERT(fBounds.isEmpty());
1451 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001452 if (!fBounds.isEmpty()) {
1453 SkASSERT(fBounds.contains(bounds));
1454 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001455 }
reed@android.come522ca52009-11-23 20:10:41 +00001456 }
1457 }
reed@google.com10296cc2011-09-21 12:29:05 +00001458
1459 uint32_t mask = 0;
1460 for (int i = 0; i < fVerbs.count(); i++) {
1461 switch (fVerbs[i]) {
1462 case kLine_Verb:
1463 mask |= kLine_SegmentMask;
1464 break;
1465 case kQuad_Verb:
1466 mask |= kQuad_SegmentMask;
1467 break;
1468 case kCubic_Verb:
1469 mask |= kCubic_SegmentMask;
1470 }
1471 }
1472 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001473}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474#endif
reed@android.come522ca52009-11-23 20:10:41 +00001475
reed@google.com04863fa2011-05-15 04:08:24 +00001476///////////////////////////////////////////////////////////////////////////////
1477
reed@google.com0b7b9822011-05-16 12:29:27 +00001478static int sign(SkScalar x) { return x < 0; }
1479#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001480
reed@google.com04863fa2011-05-15 04:08:24 +00001481static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001482 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001483}
1484
1485// only valid for a single contour
1486struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001487 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001488 fSign = 0;
1489 // warnings
1490 fCurrPt.set(0, 0);
1491 fVec0.set(0, 0);
1492 fVec1.set(0, 0);
1493 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001494
1495 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001496 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001497 }
1498
1499 SkPath::Convexity getConvexity() const { return fConvexity; }
1500
1501 void addPt(const SkPoint& pt) {
1502 if (SkPath::kConcave_Convexity == fConvexity) {
1503 return;
1504 }
1505
1506 if (0 == fPtCount) {
1507 fCurrPt = pt;
1508 ++fPtCount;
1509 } else {
1510 SkVector vec = pt - fCurrPt;
1511 if (vec.fX || vec.fY) {
1512 fCurrPt = pt;
1513 if (++fPtCount == 2) {
1514 fFirstVec = fVec1 = vec;
1515 } else {
1516 SkASSERT(fPtCount > 2);
1517 this->addVec(vec);
1518 }
reed@google.com85b6e392011-05-15 20:25:17 +00001519
1520 int sx = sign(vec.fX);
1521 int sy = sign(vec.fY);
1522 fDx += (sx != fSx);
1523 fDy += (sy != fSy);
1524 fSx = sx;
1525 fSy = sy;
1526
1527 if (fDx > 3 || fDy > 3) {
1528 fConvexity = SkPath::kConcave_Convexity;
1529 }
reed@google.com04863fa2011-05-15 04:08:24 +00001530 }
1531 }
1532 }
1533
1534 void close() {
1535 if (fPtCount > 2) {
1536 this->addVec(fFirstVec);
1537 }
1538 }
1539
1540private:
1541 void addVec(const SkVector& vec) {
1542 SkASSERT(vec.fX || vec.fY);
1543 fVec0 = fVec1;
1544 fVec1 = vec;
1545 int sign = CrossProductSign(fVec0, fVec1);
1546 if (0 == fSign) {
1547 fSign = sign;
1548 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001549 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001550 fConvexity = SkPath::kConcave_Convexity;
1551 }
1552 }
1553 }
1554
1555 SkPoint fCurrPt;
1556 SkVector fVec0, fVec1, fFirstVec;
1557 int fPtCount; // non-degenerate points
1558 int fSign;
1559 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001560 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001561};
1562
1563SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1564 SkPoint pts[4];
1565 SkPath::Verb verb;
1566 SkPath::Iter iter(path, true);
1567
1568 int contourCount = 0;
1569 int count;
1570 Convexicator state;
1571
1572 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1573 switch (verb) {
1574 case kMove_Verb:
1575 if (++contourCount > 1) {
1576 return kConcave_Convexity;
1577 }
1578 pts[1] = pts[0];
1579 count = 1;
1580 break;
1581 case kLine_Verb: count = 1; break;
1582 case kQuad_Verb: count = 2; break;
1583 case kCubic_Verb: count = 3; break;
1584 case kClose_Verb:
1585 state.close();
1586 count = 0;
1587 break;
1588 default:
1589 SkASSERT(!"bad verb");
1590 return kConcave_Convexity;
1591 }
1592
1593 for (int i = 1; i <= count; i++) {
1594 state.addPt(pts[i]);
1595 }
1596 // early exit
1597 if (kConcave_Convexity == state.getConvexity()) {
1598 return kConcave_Convexity;
1599 }
1600 }
1601 return state.getConvexity();
1602}