blob: 5a05b3e887f7f1f9c721fc6f6680b09c87288f82 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/sgl/SkPath.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
reed@google.comabf15c12011-01-18 20:35:51 +00005** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
reed@android.com8a1c16f2008-12-17 15:59:43 +00008**
reed@google.comabf15c12011-01-18 20:35:51 +00009** http://www.apache.org/licenses/LICENSE-2.0
reed@android.com8a1c16f2008-12-17 15:59:43 +000010**
reed@google.comabf15c12011-01-18 20:35:51 +000011** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
reed@android.com8a1c16f2008-12-17 15:59:43 +000015** limitations under the License.
16*/
17
18#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000019#include "SkReader32.h"
20#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkMath.h"
22
23////////////////////////////////////////////////////////////////////////////
24
25/* This guy's constructor/destructor bracket a path editing operation. It is
26 used when we know the bounds of the amount we are going to add to the path
27 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000028
reed@android.com8a1c16f2008-12-17 15:59:43 +000029 It captures some state about the path up front (i.e. if it already has a
30 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000031 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000032
reed@android.com6b82d1a2009-06-03 02:35:01 +000033 It also notes if the path was originally empty, and if so, sets isConvex
34 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000035 */
36class SkAutoPathBoundsUpdate {
37public:
38 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
39 this->init(path);
40 }
41
42 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
43 SkScalar right, SkScalar bottom) {
44 fRect.set(left, top, right, bottom);
45 this->init(path);
46 }
reed@google.comabf15c12011-01-18 20:35:51 +000047
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000049 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000050 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000051 fPath->fBounds = fRect;
52 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000053 } else if (!fDirty) {
reed@android.comd252db02009-04-01 18:31:44 +000054 fPath->fBounds.join(fRect);
55 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 }
57 }
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000060 SkPath* fPath;
61 SkRect fRect;
62 bool fDirty;
63 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000066 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000068 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000070 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000071 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 }
73};
74
reed@android.comd252db02009-04-01 18:31:44 +000075static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
77 bounds->set(0, 0, 0, 0);
78 } else {
79 bounds->set(pts.begin(), pts.count());
80// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
81 }
82}
83
84////////////////////////////////////////////////////////////////////////////
85
86/*
87 Stores the verbs and points as they are given to us, with exceptions:
88 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
89 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
90
91 The iterator does more cleanup, especially if forceClose == true
92 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
93 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
94 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
95*/
96
97////////////////////////////////////////////////////////////////////////////
98
reed@android.com6b82d1a2009-06-03 02:35:01 +000099SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {
reed@google.com04863fa2011-05-15 04:08:24 +0000100 fConvexity = kUnknown_Convexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000101#ifdef ANDROID
102 fGenerationID = 0;
103#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105
106SkPath::SkPath(const SkPath& src) {
107 SkDEBUGCODE(src.validate();)
108 *this = src;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000109#ifdef ANDROID
110 // the assignment operator above increments the ID so correct for that here
111 fGenerationID--;
112#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113}
114
115SkPath::~SkPath() {
116 SkDEBUGCODE(this->validate();)
117}
118
119SkPath& SkPath::operator=(const SkPath& src) {
120 SkDEBUGCODE(src.validate();)
121
122 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000123 fBounds = src.fBounds;
124 fPts = src.fPts;
125 fVerbs = src.fVerbs;
126 fFillType = src.fFillType;
127 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000128 fConvexity = src.fConvexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000129 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 }
131 SkDEBUGCODE(this->validate();)
132 return *this;
133}
134
reed@android.com3abec1d2009-03-02 05:36:20 +0000135bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000136 // note: don't need to look at isConvex or bounds, since just comparing the
137 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000138 return &a == &b ||
139 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts);
140}
141
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142void SkPath::swap(SkPath& other) {
143 SkASSERT(&other != NULL);
144
145 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000146 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 fPts.swap(other.fPts);
148 fVerbs.swap(other.fVerbs);
149 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000150 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000151 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 }
154}
155
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000156#ifdef ANDROID
157uint32_t SkPath::getGenerationID() const {
158 return fGenerationID;
159}
160#endif
161
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162void SkPath::reset() {
163 SkDEBUGCODE(this->validate();)
164
165 fPts.reset();
166 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000167 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000168 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000169 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170}
171
172void SkPath::rewind() {
173 SkDEBUGCODE(this->validate();)
174
175 fPts.rewind();
176 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000177 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000178 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000179 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180}
181
182bool SkPath::isEmpty() const {
183 SkDEBUGCODE(this->validate();)
184
185 int count = fVerbs.count();
186 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
187}
188
189bool SkPath::isRect(SkRect*) const {
190 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000191
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 SkASSERT(!"unimplemented");
193 return false;
194}
195
196int SkPath::getPoints(SkPoint copy[], int max) const {
197 SkDEBUGCODE(this->validate();)
198
199 SkASSERT(max >= 0);
200 int count = fPts.count();
201 if (copy && max > 0 && count > 0) {
202 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
203 }
204 return count;
205}
206
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000207SkPoint SkPath::getPoint(int index) const {
208 if ((unsigned)index < (unsigned)fPts.count()) {
209 return fPts[index];
210 }
211 return SkPoint::Make(0, 0);
212}
213
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214void SkPath::getLastPt(SkPoint* lastPt) const {
215 SkDEBUGCODE(this->validate();)
216
217 if (lastPt) {
218 int count = fPts.count();
219 if (count == 0) {
220 lastPt->set(0, 0);
221 } else {
222 *lastPt = fPts[count - 1];
223 }
224 }
225}
226
227void SkPath::setLastPt(SkScalar x, SkScalar y) {
228 SkDEBUGCODE(this->validate();)
229
230 int count = fPts.count();
231 if (count == 0) {
232 this->moveTo(x, y);
233 } else {
234 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000235 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 }
237}
238
reed@android.comd252db02009-04-01 18:31:44 +0000239void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000241 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242
reed@android.comd252db02009-04-01 18:31:44 +0000243 fBoundsIsDirty = false;
244 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245}
246
reed@google.com04863fa2011-05-15 04:08:24 +0000247void SkPath::setConvexity(Convexity c) {
248 if (fConvexity != c) {
249 fConvexity = c;
250 GEN_ID_INC;
251 }
252}
253
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254//////////////////////////////////////////////////////////////////////////////
255// Construction methods
256
reed@google.comb54455e2011-05-16 14:16:04 +0000257#define DIRTY_AFTER_EDIT \
258 do { \
259 fBoundsIsDirty = true; \
260 fConvexity = kUnknown_Convexity;\
261 } while (0)
262
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263void SkPath::incReserve(U16CPU inc) {
264 SkDEBUGCODE(this->validate();)
265
266 fVerbs.setReserve(fVerbs.count() + inc);
267 fPts.setReserve(fPts.count() + inc);
268
269 SkDEBUGCODE(this->validate();)
270}
271
272void SkPath::moveTo(SkScalar x, SkScalar y) {
273 SkDEBUGCODE(this->validate();)
274
275 int vc = fVerbs.count();
276 SkPoint* pt;
277
278 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
279 pt = &fPts[fPts.count() - 1];
280 } else {
281 pt = fPts.append();
282 *fVerbs.append() = kMove_Verb;
283 }
284 pt->set(x, y);
285
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000286 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000287 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288}
289
290void SkPath::rMoveTo(SkScalar x, SkScalar y) {
291 SkPoint pt;
292 this->getLastPt(&pt);
293 this->moveTo(pt.fX + x, pt.fY + y);
294}
295
296void SkPath::lineTo(SkScalar x, SkScalar y) {
297 SkDEBUGCODE(this->validate();)
298
299 if (fVerbs.count() == 0) {
300 fPts.append()->set(0, 0);
301 *fVerbs.append() = kMove_Verb;
302 }
303 fPts.append()->set(x, y);
304 *fVerbs.append() = kLine_Verb;
305
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000306 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000307 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308}
309
310void SkPath::rLineTo(SkScalar x, SkScalar y) {
311 SkPoint pt;
312 this->getLastPt(&pt);
313 this->lineTo(pt.fX + x, pt.fY + y);
314}
315
316void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
317 SkDEBUGCODE(this->validate();)
318
319 if (fVerbs.count() == 0) {
320 fPts.append()->set(0, 0);
321 *fVerbs.append() = kMove_Verb;
322 }
323
324 SkPoint* pts = fPts.append(2);
325 pts[0].set(x1, y1);
326 pts[1].set(x2, y2);
327 *fVerbs.append() = kQuad_Verb;
328
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000329 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000330 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331}
332
333void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
334 SkPoint pt;
335 this->getLastPt(&pt);
336 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
337}
338
339void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
340 SkScalar x3, SkScalar y3) {
341 SkDEBUGCODE(this->validate();)
342
343 if (fVerbs.count() == 0) {
344 fPts.append()->set(0, 0);
345 *fVerbs.append() = kMove_Verb;
346 }
347 SkPoint* pts = fPts.append(3);
348 pts[0].set(x1, y1);
349 pts[1].set(x2, y2);
350 pts[2].set(x3, y3);
351 *fVerbs.append() = kCubic_Verb;
352
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000353 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000354 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
357void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
358 SkScalar x3, SkScalar y3) {
359 SkPoint pt;
360 this->getLastPt(&pt);
361 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
362 pt.fX + x3, pt.fY + y3);
363}
364
365void SkPath::close() {
366 SkDEBUGCODE(this->validate();)
367
368 int count = fVerbs.count();
369 if (count > 0) {
370 switch (fVerbs[count - 1]) {
371 case kLine_Verb:
372 case kQuad_Verb:
373 case kCubic_Verb:
374 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000375 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376 break;
377 default:
378 // don't add a close if the prev wasn't a primitive
379 break;
380 }
381 }
382}
383
384///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000385
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386void SkPath::addRect(const SkRect& rect, Direction dir) {
387 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
388}
389
390void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
391 SkScalar bottom, Direction dir) {
392 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
393
394 this->incReserve(5);
395
396 this->moveTo(left, top);
397 if (dir == kCCW_Direction) {
398 this->lineTo(left, bottom);
399 this->lineTo(right, bottom);
400 this->lineTo(right, top);
401 } else {
402 this->lineTo(right, top);
403 this->lineTo(right, bottom);
404 this->lineTo(left, bottom);
405 }
406 this->close();
407}
408
409#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
410
411void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
412 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000413 SkScalar w = rect.width();
414 SkScalar halfW = SkScalarHalf(w);
415 SkScalar h = rect.height();
416 SkScalar halfH = SkScalarHalf(h);
417
418 if (halfW <= 0 || halfH <= 0) {
419 return;
420 }
421
reed@google.comabf15c12011-01-18 20:35:51 +0000422 bool skip_hori = rx >= halfW;
423 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424
425 if (skip_hori && skip_vert) {
426 this->addOval(rect, dir);
427 return;
428 }
reed@google.comabf15c12011-01-18 20:35:51 +0000429
430 SkAutoPathBoundsUpdate apbu(this, rect);
431
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 if (skip_hori) {
433 rx = halfW;
434 } else if (skip_vert) {
435 ry = halfH;
436 }
437
438 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
439 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
440
441 this->incReserve(17);
442 this->moveTo(rect.fRight - rx, rect.fTop);
443 if (dir == kCCW_Direction) {
444 if (!skip_hori) {
445 this->lineTo(rect.fLeft + rx, rect.fTop); // top
446 }
447 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
448 rect.fLeft, rect.fTop + ry - sy,
449 rect.fLeft, rect.fTop + ry); // top-left
450 if (!skip_vert) {
451 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
452 }
453 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
454 rect.fLeft + rx - sx, rect.fBottom,
455 rect.fLeft + rx, rect.fBottom); // bot-left
456 if (!skip_hori) {
457 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
458 }
459 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
460 rect.fRight, rect.fBottom - ry + sy,
461 rect.fRight, rect.fBottom - ry); // bot-right
462 if (!skip_vert) {
463 this->lineTo(rect.fRight, rect.fTop + ry);
464 }
465 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
466 rect.fRight - rx + sx, rect.fTop,
467 rect.fRight - rx, rect.fTop); // top-right
468 } else {
469 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
470 rect.fRight, rect.fTop + ry - sy,
471 rect.fRight, rect.fTop + ry); // top-right
472 if (!skip_vert) {
473 this->lineTo(rect.fRight, rect.fBottom - ry);
474 }
475 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
476 rect.fRight - rx + sx, rect.fBottom,
477 rect.fRight - rx, rect.fBottom); // bot-right
478 if (!skip_hori) {
479 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
480 }
481 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
482 rect.fLeft, rect.fBottom - ry + sy,
483 rect.fLeft, rect.fBottom - ry); // bot-left
484 if (!skip_vert) {
485 this->lineTo(rect.fLeft, rect.fTop + ry); // left
486 }
487 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
488 rect.fLeft + rx - sx, rect.fTop,
489 rect.fLeft + rx, rect.fTop); // top-left
490 if (!skip_hori) {
491 this->lineTo(rect.fRight - rx, rect.fTop); // top
492 }
493 }
494 this->close();
495}
496
497static void add_corner_arc(SkPath* path, const SkRect& rect,
498 SkScalar rx, SkScalar ry, int startAngle,
499 SkPath::Direction dir, bool forceMoveTo) {
500 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
501 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000502
reed@android.com8a1c16f2008-12-17 15:59:43 +0000503 SkRect r;
504 r.set(-rx, -ry, rx, ry);
505
506 switch (startAngle) {
507 case 0:
508 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
509 break;
510 case 90:
511 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
512 break;
513 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
514 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
515 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
516 }
reed@google.comabf15c12011-01-18 20:35:51 +0000517
reed@android.com8a1c16f2008-12-17 15:59:43 +0000518 SkScalar start = SkIntToScalar(startAngle);
519 SkScalar sweep = SkIntToScalar(90);
520 if (SkPath::kCCW_Direction == dir) {
521 start += sweep;
522 sweep = -sweep;
523 }
reed@google.comabf15c12011-01-18 20:35:51 +0000524
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525 path->arcTo(r, start, sweep, forceMoveTo);
526}
527
528void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
529 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000530 // abort before we invoke SkAutoPathBoundsUpdate()
531 if (rect.isEmpty()) {
532 return;
533 }
534
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 SkAutoPathBoundsUpdate apbu(this, rect);
536
537 if (kCW_Direction == dir) {
538 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
539 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
540 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
541 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
542 } else {
543 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
544 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
545 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
546 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
547 }
548 this->close();
549}
550
551void SkPath::addOval(const SkRect& oval, Direction dir) {
552 SkAutoPathBoundsUpdate apbu(this, oval);
553
554 SkScalar cx = oval.centerX();
555 SkScalar cy = oval.centerY();
556 SkScalar rx = SkScalarHalf(oval.width());
557 SkScalar ry = SkScalarHalf(oval.height());
558#if 0 // these seem faster than using quads (1/2 the number of edges)
559 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
560 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
561
562 this->incReserve(13);
563 this->moveTo(cx + rx, cy);
564 if (dir == kCCW_Direction) {
565 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
566 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
567 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
568 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
569 } else {
570 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
571 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
572 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
573 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
574 }
575#else
576 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
577 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
578 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
579 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
580
581 /*
582 To handle imprecision in computing the center and radii, we revert to
583 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
584 to ensure that we don't exceed the oval's bounds *ever*, since we want
585 to use oval for our fast-bounds, rather than have to recompute it.
586 */
587 const SkScalar L = oval.fLeft; // cx - rx
588 const SkScalar T = oval.fTop; // cy - ry
589 const SkScalar R = oval.fRight; // cx + rx
590 const SkScalar B = oval.fBottom; // cy + ry
591
592 this->incReserve(17); // 8 quads + close
593 this->moveTo(R, cy);
594 if (dir == kCCW_Direction) {
595 this->quadTo( R, cy - sy, cx + mx, cy - my);
596 this->quadTo(cx + sx, T, cx , T);
597 this->quadTo(cx - sx, T, cx - mx, cy - my);
598 this->quadTo( L, cy - sy, L, cy );
599 this->quadTo( L, cy + sy, cx - mx, cy + my);
600 this->quadTo(cx - sx, B, cx , B);
601 this->quadTo(cx + sx, B, cx + mx, cy + my);
602 this->quadTo( R, cy + sy, R, cy );
603 } else {
604 this->quadTo( R, cy + sy, cx + mx, cy + my);
605 this->quadTo(cx + sx, B, cx , B);
606 this->quadTo(cx - sx, B, cx - mx, cy + my);
607 this->quadTo( L, cy + sy, L, cy );
608 this->quadTo( L, cy - sy, cx - mx, cy - my);
609 this->quadTo(cx - sx, T, cx , T);
610 this->quadTo(cx + sx, T, cx + mx, cy - my);
611 this->quadTo( R, cy - sy, R, cy );
612 }
613#endif
614 this->close();
615}
616
617void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
618 if (r > 0) {
619 SkRect rect;
620 rect.set(x - r, y - r, x + r, y + r);
621 this->addOval(rect, dir);
622 }
623}
624
625#include "SkGeometry.h"
626
627static int build_arc_points(const SkRect& oval, SkScalar startAngle,
628 SkScalar sweepAngle,
629 SkPoint pts[kSkBuildQuadArcStorage]) {
630 SkVector start, stop;
631
632 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
633 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
634 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000635
636 /* If the sweep angle is nearly (but less than) 360, then due to precision
637 loss in radians-conversion and/or sin/cos, we may end up with coincident
638 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
639 of drawing a nearly complete circle (good).
640 e.g. canvas.drawArc(0, 359.99, ...)
641 -vs- canvas.drawArc(0, 359.9, ...)
642 We try to detect this edge case, and tweak the stop vector
643 */
644 if (start == stop) {
645 SkScalar sw = SkScalarAbs(sweepAngle);
646 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
647 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
648 // make a guess at a tiny angle (in radians) to tweak by
649 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
650 // not sure how much will be enough, so we use a loop
651 do {
652 stopRad -= deltaRad;
653 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
654 } while (start == stop);
655 }
656 }
657
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000659
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
661 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000662
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 return SkBuildQuadArc(start, stop,
664 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
665 &matrix, pts);
666}
667
668void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
669 bool forceMoveTo) {
670 if (oval.width() < 0 || oval.height() < 0) {
671 return;
672 }
673
674 SkPoint pts[kSkBuildQuadArcStorage];
675 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
676 SkASSERT((count & 1) == 1);
677
678 if (fVerbs.count() == 0) {
679 forceMoveTo = true;
680 }
681 this->incReserve(count);
682 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
683 for (int i = 1; i < count; i += 2) {
684 this->quadTo(pts[i], pts[i+1]);
685 }
686}
687
688void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
689 SkScalar sweepAngle) {
690 if (oval.isEmpty() || 0 == sweepAngle) {
691 return;
692 }
693
694 const SkScalar kFullCircleAngle = SkIntToScalar(360);
695
696 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
697 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
698 return;
699 }
700
701 SkPoint pts[kSkBuildQuadArcStorage];
702 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
703
704 this->incReserve(count);
705 this->moveTo(pts[0]);
706 for (int i = 1; i < count; i += 2) {
707 this->quadTo(pts[i], pts[i+1]);
708 }
709}
710
711/*
712 Need to handle the case when the angle is sharp, and our computed end-points
713 for the arc go behind pt1 and/or p2...
714*/
715void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
716 SkScalar radius) {
717 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000718
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 // need to know our prev pt so we can construct tangent vectors
720 {
721 SkPoint start;
722 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000723 // Handle degenerate cases by adding a line to the first point and
724 // bailing out.
725 if ((x1 == start.fX && y1 == start.fY) ||
726 (x1 == x2 && y1 == y2) ||
727 radius == 0) {
728 this->lineTo(x1, y1);
729 return;
730 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731 before.setNormalize(x1 - start.fX, y1 - start.fY);
732 after.setNormalize(x2 - x1, y2 - y1);
733 }
reed@google.comabf15c12011-01-18 20:35:51 +0000734
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkScalar cosh = SkPoint::DotProduct(before, after);
736 SkScalar sinh = SkPoint::CrossProduct(before, after);
737
738 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000739 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740 return;
741 }
reed@google.comabf15c12011-01-18 20:35:51 +0000742
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
744 if (dist < 0) {
745 dist = -dist;
746 }
747
748 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
749 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
750 SkRotationDirection arcDir;
751
752 // now turn before/after into normals
753 if (sinh > 0) {
754 before.rotateCCW();
755 after.rotateCCW();
756 arcDir = kCW_SkRotationDirection;
757 } else {
758 before.rotateCW();
759 after.rotateCW();
760 arcDir = kCCW_SkRotationDirection;
761 }
762
763 SkMatrix matrix;
764 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 matrix.setScale(radius, radius);
767 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
768 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000769
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772 this->incReserve(count);
773 // [xx,yy] == pts[0]
774 this->lineTo(xx, yy);
775 for (int i = 1; i < count; i += 2) {
776 this->quadTo(pts[i], pts[i+1]);
777 }
778}
779
780///////////////////////////////////////////////////////////////////////////////
781
782void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
783 SkMatrix matrix;
784
785 matrix.setTranslate(dx, dy);
786 this->addPath(path, matrix);
787}
788
789void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
790 this->incReserve(path.fPts.count());
791
792 Iter iter(path, false);
793 SkPoint pts[4];
794 Verb verb;
795
796 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
797
798 while ((verb = iter.next(pts)) != kDone_Verb) {
799 switch (verb) {
800 case kMove_Verb:
801 proc(matrix, &pts[0], &pts[0], 1);
802 this->moveTo(pts[0]);
803 break;
804 case kLine_Verb:
805 proc(matrix, &pts[1], &pts[1], 1);
806 this->lineTo(pts[1]);
807 break;
808 case kQuad_Verb:
809 proc(matrix, &pts[1], &pts[1], 2);
810 this->quadTo(pts[1], pts[2]);
811 break;
812 case kCubic_Verb:
813 proc(matrix, &pts[1], &pts[1], 3);
814 this->cubicTo(pts[1], pts[2], pts[3]);
815 break;
816 case kClose_Verb:
817 this->close();
818 break;
819 default:
820 SkASSERT(!"unknown verb");
821 }
822 }
823}
824
825///////////////////////////////////////////////////////////////////////////////
826
827static const uint8_t gPtsInVerb[] = {
828 1, // kMove
829 1, // kLine
830 2, // kQuad
831 3, // kCubic
832 0, // kClose
833 0 // kDone
834};
835
836// ignore the initial moveto, and stop when the 1st contour ends
837void SkPath::pathTo(const SkPath& path) {
838 int i, vcount = path.fVerbs.count();
839 if (vcount == 0) {
840 return;
841 }
842
843 this->incReserve(vcount);
844
845 const uint8_t* verbs = path.fVerbs.begin();
846 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
847
848 SkASSERT(verbs[0] == kMove_Verb);
849 for (i = 1; i < vcount; i++) {
850 switch (verbs[i]) {
851 case kLine_Verb:
852 this->lineTo(pts[0].fX, pts[0].fY);
853 break;
854 case kQuad_Verb:
855 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
856 break;
857 case kCubic_Verb:
858 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
859 pts[2].fX, pts[2].fY);
860 break;
861 case kClose_Verb:
862 return;
863 }
864 pts += gPtsInVerb[verbs[i]];
865 }
866}
867
868// ignore the last point of the 1st contour
869void SkPath::reversePathTo(const SkPath& path) {
870 int i, vcount = path.fVerbs.count();
871 if (vcount == 0) {
872 return;
873 }
874
875 this->incReserve(vcount);
876
877 const uint8_t* verbs = path.fVerbs.begin();
878 const SkPoint* pts = path.fPts.begin();
879
880 SkASSERT(verbs[0] == kMove_Verb);
881 for (i = 1; i < vcount; i++) {
882 int n = gPtsInVerb[verbs[i]];
883 if (n == 0) {
884 break;
885 }
886 pts += n;
887 }
888
889 while (--i > 0) {
890 switch (verbs[i]) {
891 case kLine_Verb:
892 this->lineTo(pts[-1].fX, pts[-1].fY);
893 break;
894 case kQuad_Verb:
895 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
896 break;
897 case kCubic_Verb:
898 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
899 pts[-3].fX, pts[-3].fY);
900 break;
901 default:
902 SkASSERT(!"bad verb");
903 break;
904 }
905 pts -= gPtsInVerb[verbs[i]];
906 }
907}
908
909///////////////////////////////////////////////////////////////////////////////
910
911void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
912 SkMatrix matrix;
913
914 matrix.setTranslate(dx, dy);
915 this->transform(matrix, dst);
916}
917
918#include "SkGeometry.h"
919
920static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
921 int level = 2) {
922 if (--level >= 0) {
923 SkPoint tmp[5];
924
925 SkChopQuadAtHalf(pts, tmp);
926 subdivide_quad_to(path, &tmp[0], level);
927 subdivide_quad_to(path, &tmp[2], level);
928 } else {
929 path->quadTo(pts[1], pts[2]);
930 }
931}
932
933static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
934 int level = 2) {
935 if (--level >= 0) {
936 SkPoint tmp[7];
937
938 SkChopCubicAtHalf(pts, tmp);
939 subdivide_cubic_to(path, &tmp[0], level);
940 subdivide_cubic_to(path, &tmp[3], level);
941 } else {
942 path->cubicTo(pts[1], pts[2], pts[3]);
943 }
944}
945
946void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
947 SkDEBUGCODE(this->validate();)
948 if (dst == NULL) {
949 dst = (SkPath*)this;
950 }
951
tomhudson@google.com8d430182011-06-06 19:11:19 +0000952 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 SkPath tmp;
954 tmp.fFillType = fFillType;
955
956 SkPath::Iter iter(*this, false);
957 SkPoint pts[4];
958 SkPath::Verb verb;
959
960 while ((verb = iter.next(pts)) != kDone_Verb) {
961 switch (verb) {
962 case kMove_Verb:
963 tmp.moveTo(pts[0]);
964 break;
965 case kLine_Verb:
966 tmp.lineTo(pts[1]);
967 break;
968 case kQuad_Verb:
969 subdivide_quad_to(&tmp, pts);
970 break;
971 case kCubic_Verb:
972 subdivide_cubic_to(&tmp, pts);
973 break;
974 case kClose_Verb:
975 tmp.close();
976 break;
977 default:
978 SkASSERT(!"unknown verb");
979 break;
980 }
981 }
982
983 dst->swap(tmp);
984 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
985 } else {
986 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +0000987 // fBoundsIsDirty before we set it
988 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +0000990 matrix.mapRect(&dst->fBounds, fBounds);
991 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000993 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +0000994 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 }
996
997 if (this != dst) {
998 dst->fVerbs = fVerbs;
999 dst->fPts.setCount(fPts.count());
1000 dst->fFillType = fFillType;
1001 }
1002 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1003 SkDEBUGCODE(dst->validate();)
1004 }
1005}
1006
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007///////////////////////////////////////////////////////////////////////////////
1008///////////////////////////////////////////////////////////////////////////////
1009
1010enum NeedMoveToState {
1011 kAfterClose_NeedMoveToState,
1012 kAfterCons_NeedMoveToState,
1013 kAfterPrefix_NeedMoveToState
1014};
1015
1016SkPath::Iter::Iter() {
1017#ifdef SK_DEBUG
1018 fPts = NULL;
1019 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1020 fForceClose = fNeedMoveTo = fCloseLine = false;
1021#endif
1022 // need to init enough to make next() harmlessly return kDone_Verb
1023 fVerbs = NULL;
1024 fVerbStop = NULL;
1025 fNeedClose = false;
1026}
1027
1028SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1029 this->setPath(path, forceClose);
1030}
1031
1032void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1033 fPts = path.fPts.begin();
1034 fVerbs = path.fVerbs.begin();
1035 fVerbStop = path.fVerbs.end();
1036 fForceClose = SkToU8(forceClose);
1037 fNeedClose = false;
1038 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1039}
1040
1041bool SkPath::Iter::isClosedContour() const {
1042 if (fVerbs == NULL || fVerbs == fVerbStop) {
1043 return false;
1044 }
1045 if (fForceClose) {
1046 return true;
1047 }
1048
1049 const uint8_t* verbs = fVerbs;
1050 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001051
reed@android.com8a1c16f2008-12-17 15:59:43 +00001052 if (kMove_Verb == *verbs) {
1053 verbs += 1; // skip the initial moveto
1054 }
1055
1056 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001057 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 if (kMove_Verb == v) {
1059 break;
1060 }
1061 if (kClose_Verb == v) {
1062 return true;
1063 }
1064 }
1065 return false;
1066}
1067
1068SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1069 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001070 // A special case: if both points are NaN, SkPoint::operation== returns
1071 // false, but the iterator expects that they are treated as the same.
1072 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001073 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1074 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001075 return kClose_Verb;
1076 }
1077
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 if (pts) {
1079 pts[0] = fLastPt;
1080 pts[1] = fMoveTo;
1081 }
1082 fLastPt = fMoveTo;
1083 fCloseLine = true;
1084 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001085 } else {
1086 pts[0] = fMoveTo;
1087 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001088 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089}
1090
1091bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1092 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1093 if (pts) {
1094 *pts = fMoveTo;
1095 }
1096 fNeedClose = fForceClose;
1097 fNeedMoveTo = kAfterCons_NeedMoveToState;
1098 fVerbs -= 1;
1099 return true;
1100 }
1101
1102 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1103 if (pts) {
1104 *pts = fMoveTo;
1105 }
1106 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1107 } else {
1108 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1109 if (pts) {
1110 *pts = fPts[-1];
1111 }
1112 }
1113 return false;
1114}
1115
1116SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1117 if (fVerbs == fVerbStop) {
1118 if (fNeedClose) {
1119 if (kLine_Verb == this->autoClose(pts)) {
1120 return kLine_Verb;
1121 }
1122 fNeedClose = false;
1123 return kClose_Verb;
1124 }
1125 return kDone_Verb;
1126 }
1127
1128 unsigned verb = *fVerbs++;
1129 const SkPoint* srcPts = fPts;
1130
1131 switch (verb) {
1132 case kMove_Verb:
1133 if (fNeedClose) {
1134 fVerbs -= 1;
1135 verb = this->autoClose(pts);
1136 if (verb == kClose_Verb) {
1137 fNeedClose = false;
1138 }
1139 return (Verb)verb;
1140 }
1141 if (fVerbs == fVerbStop) { // might be a trailing moveto
1142 return kDone_Verb;
1143 }
1144 fMoveTo = *srcPts;
1145 if (pts) {
1146 pts[0] = *srcPts;
1147 }
1148 srcPts += 1;
1149 fNeedMoveTo = kAfterCons_NeedMoveToState;
1150 fNeedClose = fForceClose;
1151 break;
1152 case kLine_Verb:
1153 if (this->cons_moveTo(pts)) {
1154 return kMove_Verb;
1155 }
1156 if (pts) {
1157 pts[1] = srcPts[0];
1158 }
1159 fLastPt = srcPts[0];
1160 fCloseLine = false;
1161 srcPts += 1;
1162 break;
1163 case kQuad_Verb:
1164 if (this->cons_moveTo(pts)) {
1165 return kMove_Verb;
1166 }
1167 if (pts) {
1168 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1169 }
1170 fLastPt = srcPts[1];
1171 srcPts += 2;
1172 break;
1173 case kCubic_Verb:
1174 if (this->cons_moveTo(pts)) {
1175 return kMove_Verb;
1176 }
1177 if (pts) {
1178 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1179 }
1180 fLastPt = srcPts[2];
1181 srcPts += 3;
1182 break;
1183 case kClose_Verb:
1184 verb = this->autoClose(pts);
1185 if (verb == kLine_Verb) {
1186 fVerbs -= 1;
1187 } else {
1188 fNeedClose = false;
1189 }
1190 fNeedMoveTo = kAfterClose_NeedMoveToState;
1191 break;
1192 }
1193 fPts = srcPts;
1194 return (Verb)verb;
1195}
1196
1197///////////////////////////////////////////////////////////////////////////////
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199/*
1200 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1201*/
1202
reed@google.com73945652011-04-25 19:04:27 +00001203void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 SkDEBUGCODE(this->validate();)
1205
1206 buffer.write32(fPts.count());
1207 buffer.write32(fVerbs.count());
1208 buffer.write32(fFillType);
1209 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1210 buffer.writePad(fVerbs.begin(), fVerbs.count());
1211}
1212
reed@google.com73945652011-04-25 19:04:27 +00001213void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 fPts.setCount(buffer.readS32());
1215 fVerbs.setCount(buffer.readS32());
1216 fFillType = buffer.readS32();
1217 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1218 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001219
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001220 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001221 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222
1223 SkDEBUGCODE(this->validate();)
1224}
1225
1226///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227///////////////////////////////////////////////////////////////////////////////
1228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229void SkPath::dump(bool forceClose, const char title[]) const {
1230 Iter iter(*this, forceClose);
1231 SkPoint pts[4];
1232 Verb verb;
1233
1234 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1235 title ? title : "");
1236
1237 while ((verb = iter.next(pts)) != kDone_Verb) {
1238 switch (verb) {
1239 case kMove_Verb:
1240#ifdef SK_CAN_USE_FLOAT
1241 SkDebugf(" path: moveTo [%g %g]\n",
1242 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1243#else
1244 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1245#endif
1246 break;
1247 case kLine_Verb:
1248#ifdef SK_CAN_USE_FLOAT
1249 SkDebugf(" path: lineTo [%g %g]\n",
1250 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1251#else
1252 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1253#endif
1254 break;
1255 case kQuad_Verb:
1256#ifdef SK_CAN_USE_FLOAT
1257 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1258 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1259 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1260#else
1261 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1262 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1263#endif
1264 break;
1265 case kCubic_Verb:
1266#ifdef SK_CAN_USE_FLOAT
1267 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1268 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1269 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1270 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1271#else
1272 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1273 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1274 pts[3].fX, pts[3].fY);
1275#endif
1276 break;
1277 case kClose_Verb:
1278 SkDebugf(" path: close\n");
1279 break;
1280 default:
1281 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1282 verb = kDone_Verb; // stop the loop
1283 break;
1284 }
1285 }
1286 SkDebugf("path: done %s\n", title ? title : "");
1287}
1288
reed@android.come522ca52009-11-23 20:10:41 +00001289void SkPath::dump() const {
1290 this->dump(false);
1291}
1292
1293#ifdef SK_DEBUG
1294void SkPath::validate() const {
1295 SkASSERT(this != NULL);
1296 SkASSERT((fFillType & ~3) == 0);
1297 fPts.validate();
1298 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001299
reed@android.come522ca52009-11-23 20:10:41 +00001300 if (!fBoundsIsDirty) {
1301 SkRect bounds;
1302 compute_pt_bounds(&bounds, fPts);
1303 if (fPts.count() <= 1) {
1304 // if we're empty, fBounds may be empty but translated, so we can't
1305 // necessarily compare to bounds directly
1306 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1307 // be [2, 2, 2, 2]
1308 SkASSERT(bounds.isEmpty());
1309 SkASSERT(fBounds.isEmpty());
1310 } else {
1311 fBounds.contains(bounds);
1312 }
1313 }
1314}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315#endif
reed@android.come522ca52009-11-23 20:10:41 +00001316
reed@google.com04863fa2011-05-15 04:08:24 +00001317///////////////////////////////////////////////////////////////////////////////
1318
1319/**
1320 * Returns -1 || 0 || 1 depending on the sign of value:
1321 * -1 if value < 0
1322 * 0 if vlaue == 0
1323 * 1 if value > 0
1324 */
1325static int SkScalarSign(SkScalar value) {
1326 return value < 0 ? -1 : (value > 0);
1327}
1328
reed@google.com0b7b9822011-05-16 12:29:27 +00001329static int sign(SkScalar x) { return x < 0; }
1330#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001331
reed@google.com04863fa2011-05-15 04:08:24 +00001332static int CrossProductSign(const SkVector& a, const SkVector& b) {
1333 return SkScalarSign(SkPoint::CrossProduct(a, b));
1334}
1335
1336// only valid for a single contour
1337struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001338 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001339 fSign = 0;
1340 // warnings
1341 fCurrPt.set(0, 0);
1342 fVec0.set(0, 0);
1343 fVec1.set(0, 0);
1344 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001345
1346 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001347 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001348 }
1349
1350 SkPath::Convexity getConvexity() const { return fConvexity; }
1351
1352 void addPt(const SkPoint& pt) {
1353 if (SkPath::kConcave_Convexity == fConvexity) {
1354 return;
1355 }
1356
1357 if (0 == fPtCount) {
1358 fCurrPt = pt;
1359 ++fPtCount;
1360 } else {
1361 SkVector vec = pt - fCurrPt;
1362 if (vec.fX || vec.fY) {
1363 fCurrPt = pt;
1364 if (++fPtCount == 2) {
1365 fFirstVec = fVec1 = vec;
1366 } else {
1367 SkASSERT(fPtCount > 2);
1368 this->addVec(vec);
1369 }
reed@google.com85b6e392011-05-15 20:25:17 +00001370
1371 int sx = sign(vec.fX);
1372 int sy = sign(vec.fY);
1373 fDx += (sx != fSx);
1374 fDy += (sy != fSy);
1375 fSx = sx;
1376 fSy = sy;
1377
1378 if (fDx > 3 || fDy > 3) {
1379 fConvexity = SkPath::kConcave_Convexity;
1380 }
reed@google.com04863fa2011-05-15 04:08:24 +00001381 }
1382 }
1383 }
1384
1385 void close() {
1386 if (fPtCount > 2) {
1387 this->addVec(fFirstVec);
1388 }
1389 }
1390
1391private:
1392 void addVec(const SkVector& vec) {
1393 SkASSERT(vec.fX || vec.fY);
1394 fVec0 = fVec1;
1395 fVec1 = vec;
1396 int sign = CrossProductSign(fVec0, fVec1);
1397 if (0 == fSign) {
1398 fSign = sign;
1399 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001400 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001401 fConvexity = SkPath::kConcave_Convexity;
1402 }
1403 }
1404 }
1405
1406 SkPoint fCurrPt;
1407 SkVector fVec0, fVec1, fFirstVec;
1408 int fPtCount; // non-degenerate points
1409 int fSign;
1410 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001411 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001412};
1413
1414SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1415 SkPoint pts[4];
1416 SkPath::Verb verb;
1417 SkPath::Iter iter(path, true);
1418
1419 int contourCount = 0;
1420 int count;
1421 Convexicator state;
1422
1423 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1424 switch (verb) {
1425 case kMove_Verb:
1426 if (++contourCount > 1) {
1427 return kConcave_Convexity;
1428 }
1429 pts[1] = pts[0];
1430 count = 1;
1431 break;
1432 case kLine_Verb: count = 1; break;
1433 case kQuad_Verb: count = 2; break;
1434 case kCubic_Verb: count = 3; break;
1435 case kClose_Verb:
1436 state.close();
1437 count = 0;
1438 break;
1439 default:
1440 SkASSERT(!"bad verb");
1441 return kConcave_Convexity;
1442 }
1443
1444 for (int i = 1; i <= count; i++) {
1445 state.addPt(pts[i]);
1446 }
1447 // early exit
1448 if (kConcave_Convexity == state.getConvexity()) {
1449 return kConcave_Convexity;
1450 }
1451 }
1452 return state.getConvexity();
1453}