blob: 0aa741a3eab006f863c6f23778b0462bd1e52117 [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"
19#include "SkFlattenable.h"
20#include "SkMath.h"
21
22////////////////////////////////////////////////////////////////////////////
23
24/* This guy's constructor/destructor bracket a path editing operation. It is
25 used when we know the bounds of the amount we are going to add to the path
26 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000027
reed@android.com8a1c16f2008-12-17 15:59:43 +000028 It captures some state about the path up front (i.e. if it already has a
29 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000030 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000031
reed@android.com6b82d1a2009-06-03 02:35:01 +000032 It also notes if the path was originally empty, and if so, sets isConvex
33 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000034 */
35class SkAutoPathBoundsUpdate {
36public:
37 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
38 this->init(path);
39 }
40
41 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
42 SkScalar right, SkScalar bottom) {
43 fRect.set(left, top, right, bottom);
44 this->init(path);
45 }
reed@google.comabf15c12011-01-18 20:35:51 +000046
reed@android.com8a1c16f2008-12-17 15:59:43 +000047 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000048 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000049 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000050 fPath->fBounds = fRect;
51 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000052 } else if (!fDirty) {
reed@android.comd252db02009-04-01 18:31:44 +000053 fPath->fBounds.join(fRect);
54 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 }
56 }
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000059 SkPath* fPath;
60 SkRect fRect;
61 bool fDirty;
62 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000063
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000065 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000067 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000069 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000070 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 }
72};
73
reed@android.comd252db02009-04-01 18:31:44 +000074static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
76 bounds->set(0, 0, 0, 0);
77 } else {
78 bounds->set(pts.begin(), pts.count());
79// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
80 }
81}
82
83////////////////////////////////////////////////////////////////////////////
84
85/*
86 Stores the verbs and points as they are given to us, with exceptions:
87 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
88 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
89
90 The iterator does more cleanup, especially if forceClose == true
91 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
92 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
93 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
94*/
95
96////////////////////////////////////////////////////////////////////////////
97
reed@android.com6b82d1a2009-06-03 02:35:01 +000098SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {
99 fIsConvex = false;
100}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101
102SkPath::SkPath(const SkPath& src) {
103 SkDEBUGCODE(src.validate();)
104 *this = src;
105}
106
107SkPath::~SkPath() {
108 SkDEBUGCODE(this->validate();)
109}
110
111SkPath& SkPath::operator=(const SkPath& src) {
112 SkDEBUGCODE(src.validate();)
113
114 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000115 fBounds = src.fBounds;
116 fPts = src.fPts;
117 fVerbs = src.fVerbs;
118 fFillType = src.fFillType;
119 fBoundsIsDirty = src.fBoundsIsDirty;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000120 fIsConvex = src.fIsConvex;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 }
122 SkDEBUGCODE(this->validate();)
123 return *this;
124}
125
reed@android.com3abec1d2009-03-02 05:36:20 +0000126bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000127 // note: don't need to look at isConvex or bounds, since just comparing the
128 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000129 return &a == &b ||
130 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts);
131}
132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133void SkPath::swap(SkPath& other) {
134 SkASSERT(&other != NULL);
135
136 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000137 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 fPts.swap(other.fPts);
139 fVerbs.swap(other.fVerbs);
140 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000141 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142 SkTSwap<uint8_t>(fIsConvex, other.fIsConvex);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 }
144}
145
146void SkPath::reset() {
147 SkDEBUGCODE(this->validate();)
148
149 fPts.reset();
150 fVerbs.reset();
reed@android.comd252db02009-04-01 18:31:44 +0000151 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152}
153
154void SkPath::rewind() {
155 SkDEBUGCODE(this->validate();)
156
157 fPts.rewind();
158 fVerbs.rewind();
reed@android.comd252db02009-04-01 18:31:44 +0000159 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160}
161
162bool SkPath::isEmpty() const {
163 SkDEBUGCODE(this->validate();)
164
165 int count = fVerbs.count();
166 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
167}
168
169bool SkPath::isRect(SkRect*) const {
170 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 SkASSERT(!"unimplemented");
173 return false;
174}
175
176int SkPath::getPoints(SkPoint copy[], int max) const {
177 SkDEBUGCODE(this->validate();)
178
179 SkASSERT(max >= 0);
180 int count = fPts.count();
181 if (copy && max > 0 && count > 0) {
182 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
183 }
184 return count;
185}
186
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000187SkPoint SkPath::getPoint(int index) const {
188 if ((unsigned)index < (unsigned)fPts.count()) {
189 return fPts[index];
190 }
191 return SkPoint::Make(0, 0);
192}
193
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194void SkPath::getLastPt(SkPoint* lastPt) const {
195 SkDEBUGCODE(this->validate();)
196
197 if (lastPt) {
198 int count = fPts.count();
199 if (count == 0) {
200 lastPt->set(0, 0);
201 } else {
202 *lastPt = fPts[count - 1];
203 }
204 }
205}
206
207void SkPath::setLastPt(SkScalar x, SkScalar y) {
208 SkDEBUGCODE(this->validate();)
209
210 int count = fPts.count();
211 if (count == 0) {
212 this->moveTo(x, y);
213 } else {
214 fPts[count - 1].set(x, y);
215 }
216}
217
reed@android.comd252db02009-04-01 18:31:44 +0000218void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000220 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221
reed@android.comd252db02009-04-01 18:31:44 +0000222 fBoundsIsDirty = false;
223 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224}
225
226//////////////////////////////////////////////////////////////////////////////
227// Construction methods
228
229void SkPath::incReserve(U16CPU inc) {
230 SkDEBUGCODE(this->validate();)
231
232 fVerbs.setReserve(fVerbs.count() + inc);
233 fPts.setReserve(fPts.count() + inc);
234
235 SkDEBUGCODE(this->validate();)
236}
237
238void SkPath::moveTo(SkScalar x, SkScalar y) {
239 SkDEBUGCODE(this->validate();)
240
241 int vc = fVerbs.count();
242 SkPoint* pt;
243
244 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
245 pt = &fPts[fPts.count() - 1];
246 } else {
247 pt = fPts.append();
248 *fVerbs.append() = kMove_Verb;
249 }
250 pt->set(x, y);
251
reed@android.comd252db02009-04-01 18:31:44 +0000252 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253}
254
255void SkPath::rMoveTo(SkScalar x, SkScalar y) {
256 SkPoint pt;
257 this->getLastPt(&pt);
258 this->moveTo(pt.fX + x, pt.fY + y);
259}
260
261void SkPath::lineTo(SkScalar x, SkScalar y) {
262 SkDEBUGCODE(this->validate();)
263
264 if (fVerbs.count() == 0) {
265 fPts.append()->set(0, 0);
266 *fVerbs.append() = kMove_Verb;
267 }
268 fPts.append()->set(x, y);
269 *fVerbs.append() = kLine_Verb;
270
reed@android.comd252db02009-04-01 18:31:44 +0000271 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000272}
273
274void SkPath::rLineTo(SkScalar x, SkScalar y) {
275 SkPoint pt;
276 this->getLastPt(&pt);
277 this->lineTo(pt.fX + x, pt.fY + y);
278}
279
280void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
281 SkDEBUGCODE(this->validate();)
282
283 if (fVerbs.count() == 0) {
284 fPts.append()->set(0, 0);
285 *fVerbs.append() = kMove_Verb;
286 }
287
288 SkPoint* pts = fPts.append(2);
289 pts[0].set(x1, y1);
290 pts[1].set(x2, y2);
291 *fVerbs.append() = kQuad_Verb;
292
reed@android.comd252db02009-04-01 18:31:44 +0000293 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294}
295
296void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
297 SkPoint pt;
298 this->getLastPt(&pt);
299 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
300}
301
302void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
303 SkScalar x3, SkScalar y3) {
304 SkDEBUGCODE(this->validate();)
305
306 if (fVerbs.count() == 0) {
307 fPts.append()->set(0, 0);
308 *fVerbs.append() = kMove_Verb;
309 }
310 SkPoint* pts = fPts.append(3);
311 pts[0].set(x1, y1);
312 pts[1].set(x2, y2);
313 pts[2].set(x3, y3);
314 *fVerbs.append() = kCubic_Verb;
315
reed@android.comd252db02009-04-01 18:31:44 +0000316 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317}
318
319void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
320 SkScalar x3, SkScalar y3) {
321 SkPoint pt;
322 this->getLastPt(&pt);
323 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
324 pt.fX + x3, pt.fY + y3);
325}
326
327void SkPath::close() {
328 SkDEBUGCODE(this->validate();)
329
330 int count = fVerbs.count();
331 if (count > 0) {
332 switch (fVerbs[count - 1]) {
333 case kLine_Verb:
334 case kQuad_Verb:
335 case kCubic_Verb:
336 *fVerbs.append() = kClose_Verb;
337 break;
338 default:
339 // don't add a close if the prev wasn't a primitive
340 break;
341 }
342 }
343}
344
345///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000346
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347void SkPath::addRect(const SkRect& rect, Direction dir) {
348 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
349}
350
351void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
352 SkScalar bottom, Direction dir) {
353 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
354
355 this->incReserve(5);
356
357 this->moveTo(left, top);
358 if (dir == kCCW_Direction) {
359 this->lineTo(left, bottom);
360 this->lineTo(right, bottom);
361 this->lineTo(right, top);
362 } else {
363 this->lineTo(right, top);
364 this->lineTo(right, bottom);
365 this->lineTo(left, bottom);
366 }
367 this->close();
368}
369
370#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
371
372void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
373 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374 SkScalar w = rect.width();
375 SkScalar halfW = SkScalarHalf(w);
376 SkScalar h = rect.height();
377 SkScalar halfH = SkScalarHalf(h);
378
379 if (halfW <= 0 || halfH <= 0) {
380 return;
381 }
382
reed@google.comabf15c12011-01-18 20:35:51 +0000383 bool skip_hori = rx >= halfW;
384 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385
386 if (skip_hori && skip_vert) {
387 this->addOval(rect, dir);
388 return;
389 }
reed@google.comabf15c12011-01-18 20:35:51 +0000390
391 SkAutoPathBoundsUpdate apbu(this, rect);
392
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393 if (skip_hori) {
394 rx = halfW;
395 } else if (skip_vert) {
396 ry = halfH;
397 }
398
399 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
400 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
401
402 this->incReserve(17);
403 this->moveTo(rect.fRight - rx, rect.fTop);
404 if (dir == kCCW_Direction) {
405 if (!skip_hori) {
406 this->lineTo(rect.fLeft + rx, rect.fTop); // top
407 }
408 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
409 rect.fLeft, rect.fTop + ry - sy,
410 rect.fLeft, rect.fTop + ry); // top-left
411 if (!skip_vert) {
412 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
413 }
414 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
415 rect.fLeft + rx - sx, rect.fBottom,
416 rect.fLeft + rx, rect.fBottom); // bot-left
417 if (!skip_hori) {
418 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
419 }
420 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
421 rect.fRight, rect.fBottom - ry + sy,
422 rect.fRight, rect.fBottom - ry); // bot-right
423 if (!skip_vert) {
424 this->lineTo(rect.fRight, rect.fTop + ry);
425 }
426 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
427 rect.fRight - rx + sx, rect.fTop,
428 rect.fRight - rx, rect.fTop); // top-right
429 } else {
430 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
431 rect.fRight, rect.fTop + ry - sy,
432 rect.fRight, rect.fTop + ry); // top-right
433 if (!skip_vert) {
434 this->lineTo(rect.fRight, rect.fBottom - ry);
435 }
436 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
437 rect.fRight - rx + sx, rect.fBottom,
438 rect.fRight - rx, rect.fBottom); // bot-right
439 if (!skip_hori) {
440 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
441 }
442 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
443 rect.fLeft, rect.fBottom - ry + sy,
444 rect.fLeft, rect.fBottom - ry); // bot-left
445 if (!skip_vert) {
446 this->lineTo(rect.fLeft, rect.fTop + ry); // left
447 }
448 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
449 rect.fLeft + rx - sx, rect.fTop,
450 rect.fLeft + rx, rect.fTop); // top-left
451 if (!skip_hori) {
452 this->lineTo(rect.fRight - rx, rect.fTop); // top
453 }
454 }
455 this->close();
456}
457
458static void add_corner_arc(SkPath* path, const SkRect& rect,
459 SkScalar rx, SkScalar ry, int startAngle,
460 SkPath::Direction dir, bool forceMoveTo) {
461 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
462 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000463
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464 SkRect r;
465 r.set(-rx, -ry, rx, ry);
466
467 switch (startAngle) {
468 case 0:
469 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
470 break;
471 case 90:
472 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
473 break;
474 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
475 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
476 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
477 }
reed@google.comabf15c12011-01-18 20:35:51 +0000478
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 SkScalar start = SkIntToScalar(startAngle);
480 SkScalar sweep = SkIntToScalar(90);
481 if (SkPath::kCCW_Direction == dir) {
482 start += sweep;
483 sweep = -sweep;
484 }
reed@google.comabf15c12011-01-18 20:35:51 +0000485
reed@android.com8a1c16f2008-12-17 15:59:43 +0000486 path->arcTo(r, start, sweep, forceMoveTo);
487}
488
489void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
490 Direction dir) {
491 SkAutoPathBoundsUpdate apbu(this, rect);
492
493 if (kCW_Direction == dir) {
494 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
495 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
496 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
497 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
498 } else {
499 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
500 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
501 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
502 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
503 }
504 this->close();
505}
506
507void SkPath::addOval(const SkRect& oval, Direction dir) {
508 SkAutoPathBoundsUpdate apbu(this, oval);
509
510 SkScalar cx = oval.centerX();
511 SkScalar cy = oval.centerY();
512 SkScalar rx = SkScalarHalf(oval.width());
513 SkScalar ry = SkScalarHalf(oval.height());
514#if 0 // these seem faster than using quads (1/2 the number of edges)
515 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
516 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
517
518 this->incReserve(13);
519 this->moveTo(cx + rx, cy);
520 if (dir == kCCW_Direction) {
521 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
522 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
523 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
524 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
525 } else {
526 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
527 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
528 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
529 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
530 }
531#else
532 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
533 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
534 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
535 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
536
537 /*
538 To handle imprecision in computing the center and radii, we revert to
539 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
540 to ensure that we don't exceed the oval's bounds *ever*, since we want
541 to use oval for our fast-bounds, rather than have to recompute it.
542 */
543 const SkScalar L = oval.fLeft; // cx - rx
544 const SkScalar T = oval.fTop; // cy - ry
545 const SkScalar R = oval.fRight; // cx + rx
546 const SkScalar B = oval.fBottom; // cy + ry
547
548 this->incReserve(17); // 8 quads + close
549 this->moveTo(R, cy);
550 if (dir == kCCW_Direction) {
551 this->quadTo( R, cy - sy, cx + mx, cy - my);
552 this->quadTo(cx + sx, T, cx , T);
553 this->quadTo(cx - sx, T, cx - mx, cy - my);
554 this->quadTo( L, cy - sy, L, cy );
555 this->quadTo( L, cy + sy, cx - mx, cy + my);
556 this->quadTo(cx - sx, B, cx , B);
557 this->quadTo(cx + sx, B, cx + mx, cy + my);
558 this->quadTo( R, cy + sy, R, cy );
559 } else {
560 this->quadTo( R, cy + sy, cx + mx, cy + my);
561 this->quadTo(cx + sx, B, cx , B);
562 this->quadTo(cx - sx, B, cx - mx, cy + my);
563 this->quadTo( L, cy + sy, L, cy );
564 this->quadTo( L, cy - sy, cx - mx, cy - my);
565 this->quadTo(cx - sx, T, cx , T);
566 this->quadTo(cx + sx, T, cx + mx, cy - my);
567 this->quadTo( R, cy - sy, R, cy );
568 }
569#endif
570 this->close();
571}
572
573void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
574 if (r > 0) {
575 SkRect rect;
576 rect.set(x - r, y - r, x + r, y + r);
577 this->addOval(rect, dir);
578 }
579}
580
581#include "SkGeometry.h"
582
583static int build_arc_points(const SkRect& oval, SkScalar startAngle,
584 SkScalar sweepAngle,
585 SkPoint pts[kSkBuildQuadArcStorage]) {
586 SkVector start, stop;
587
588 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
589 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
590 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000591
592 /* If the sweep angle is nearly (but less than) 360, then due to precision
593 loss in radians-conversion and/or sin/cos, we may end up with coincident
594 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
595 of drawing a nearly complete circle (good).
596 e.g. canvas.drawArc(0, 359.99, ...)
597 -vs- canvas.drawArc(0, 359.9, ...)
598 We try to detect this edge case, and tweak the stop vector
599 */
600 if (start == stop) {
601 SkScalar sw = SkScalarAbs(sweepAngle);
602 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
603 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
604 // make a guess at a tiny angle (in radians) to tweak by
605 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
606 // not sure how much will be enough, so we use a loop
607 do {
608 stopRad -= deltaRad;
609 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
610 } while (start == stop);
611 }
612 }
613
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000615
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
617 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000618
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 return SkBuildQuadArc(start, stop,
620 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
621 &matrix, pts);
622}
623
624void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
625 bool forceMoveTo) {
626 if (oval.width() < 0 || oval.height() < 0) {
627 return;
628 }
629
630 SkPoint pts[kSkBuildQuadArcStorage];
631 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
632 SkASSERT((count & 1) == 1);
633
634 if (fVerbs.count() == 0) {
635 forceMoveTo = true;
636 }
637 this->incReserve(count);
638 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
639 for (int i = 1; i < count; i += 2) {
640 this->quadTo(pts[i], pts[i+1]);
641 }
642}
643
644void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
645 SkScalar sweepAngle) {
646 if (oval.isEmpty() || 0 == sweepAngle) {
647 return;
648 }
649
650 const SkScalar kFullCircleAngle = SkIntToScalar(360);
651
652 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
653 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
654 return;
655 }
656
657 SkPoint pts[kSkBuildQuadArcStorage];
658 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
659
660 this->incReserve(count);
661 this->moveTo(pts[0]);
662 for (int i = 1; i < count; i += 2) {
663 this->quadTo(pts[i], pts[i+1]);
664 }
665}
666
667/*
668 Need to handle the case when the angle is sharp, and our computed end-points
669 for the arc go behind pt1 and/or p2...
670*/
671void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
672 SkScalar radius) {
673 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000674
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675 // need to know our prev pt so we can construct tangent vectors
676 {
677 SkPoint start;
678 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000679 // Handle degenerate cases by adding a line to the first point and
680 // bailing out.
681 if ((x1 == start.fX && y1 == start.fY) ||
682 (x1 == x2 && y1 == y2) ||
683 radius == 0) {
684 this->lineTo(x1, y1);
685 return;
686 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 before.setNormalize(x1 - start.fX, y1 - start.fY);
688 after.setNormalize(x2 - x1, y2 - y1);
689 }
reed@google.comabf15c12011-01-18 20:35:51 +0000690
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 SkScalar cosh = SkPoint::DotProduct(before, after);
692 SkScalar sinh = SkPoint::CrossProduct(before, after);
693
694 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000695 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 return;
697 }
reed@google.comabf15c12011-01-18 20:35:51 +0000698
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
700 if (dist < 0) {
701 dist = -dist;
702 }
703
704 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
705 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
706 SkRotationDirection arcDir;
707
708 // now turn before/after into normals
709 if (sinh > 0) {
710 before.rotateCCW();
711 after.rotateCCW();
712 arcDir = kCW_SkRotationDirection;
713 } else {
714 before.rotateCW();
715 after.rotateCW();
716 arcDir = kCCW_SkRotationDirection;
717 }
718
719 SkMatrix matrix;
720 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 matrix.setScale(radius, radius);
723 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
724 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000725
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000727
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728 this->incReserve(count);
729 // [xx,yy] == pts[0]
730 this->lineTo(xx, yy);
731 for (int i = 1; i < count; i += 2) {
732 this->quadTo(pts[i], pts[i+1]);
733 }
734}
735
736///////////////////////////////////////////////////////////////////////////////
737
738void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
739 SkMatrix matrix;
740
741 matrix.setTranslate(dx, dy);
742 this->addPath(path, matrix);
743}
744
745void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
746 this->incReserve(path.fPts.count());
747
748 Iter iter(path, false);
749 SkPoint pts[4];
750 Verb verb;
751
752 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
753
754 while ((verb = iter.next(pts)) != kDone_Verb) {
755 switch (verb) {
756 case kMove_Verb:
757 proc(matrix, &pts[0], &pts[0], 1);
758 this->moveTo(pts[0]);
759 break;
760 case kLine_Verb:
761 proc(matrix, &pts[1], &pts[1], 1);
762 this->lineTo(pts[1]);
763 break;
764 case kQuad_Verb:
765 proc(matrix, &pts[1], &pts[1], 2);
766 this->quadTo(pts[1], pts[2]);
767 break;
768 case kCubic_Verb:
769 proc(matrix, &pts[1], &pts[1], 3);
770 this->cubicTo(pts[1], pts[2], pts[3]);
771 break;
772 case kClose_Verb:
773 this->close();
774 break;
775 default:
776 SkASSERT(!"unknown verb");
777 }
778 }
779}
780
781///////////////////////////////////////////////////////////////////////////////
782
783static const uint8_t gPtsInVerb[] = {
784 1, // kMove
785 1, // kLine
786 2, // kQuad
787 3, // kCubic
788 0, // kClose
789 0 // kDone
790};
791
792// ignore the initial moveto, and stop when the 1st contour ends
793void SkPath::pathTo(const SkPath& path) {
794 int i, vcount = path.fVerbs.count();
795 if (vcount == 0) {
796 return;
797 }
798
799 this->incReserve(vcount);
800
801 const uint8_t* verbs = path.fVerbs.begin();
802 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
803
804 SkASSERT(verbs[0] == kMove_Verb);
805 for (i = 1; i < vcount; i++) {
806 switch (verbs[i]) {
807 case kLine_Verb:
808 this->lineTo(pts[0].fX, pts[0].fY);
809 break;
810 case kQuad_Verb:
811 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
812 break;
813 case kCubic_Verb:
814 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
815 pts[2].fX, pts[2].fY);
816 break;
817 case kClose_Verb:
818 return;
819 }
820 pts += gPtsInVerb[verbs[i]];
821 }
822}
823
824// ignore the last point of the 1st contour
825void SkPath::reversePathTo(const SkPath& path) {
826 int i, vcount = path.fVerbs.count();
827 if (vcount == 0) {
828 return;
829 }
830
831 this->incReserve(vcount);
832
833 const uint8_t* verbs = path.fVerbs.begin();
834 const SkPoint* pts = path.fPts.begin();
835
836 SkASSERT(verbs[0] == kMove_Verb);
837 for (i = 1; i < vcount; i++) {
838 int n = gPtsInVerb[verbs[i]];
839 if (n == 0) {
840 break;
841 }
842 pts += n;
843 }
844
845 while (--i > 0) {
846 switch (verbs[i]) {
847 case kLine_Verb:
848 this->lineTo(pts[-1].fX, pts[-1].fY);
849 break;
850 case kQuad_Verb:
851 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
852 break;
853 case kCubic_Verb:
854 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
855 pts[-3].fX, pts[-3].fY);
856 break;
857 default:
858 SkASSERT(!"bad verb");
859 break;
860 }
861 pts -= gPtsInVerb[verbs[i]];
862 }
863}
864
865///////////////////////////////////////////////////////////////////////////////
866
867void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
868 SkMatrix matrix;
869
870 matrix.setTranslate(dx, dy);
871 this->transform(matrix, dst);
872}
873
874#include "SkGeometry.h"
875
876static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
877 int level = 2) {
878 if (--level >= 0) {
879 SkPoint tmp[5];
880
881 SkChopQuadAtHalf(pts, tmp);
882 subdivide_quad_to(path, &tmp[0], level);
883 subdivide_quad_to(path, &tmp[2], level);
884 } else {
885 path->quadTo(pts[1], pts[2]);
886 }
887}
888
889static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
890 int level = 2) {
891 if (--level >= 0) {
892 SkPoint tmp[7];
893
894 SkChopCubicAtHalf(pts, tmp);
895 subdivide_cubic_to(path, &tmp[0], level);
896 subdivide_cubic_to(path, &tmp[3], level);
897 } else {
898 path->cubicTo(pts[1], pts[2], pts[3]);
899 }
900}
901
902void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
903 SkDEBUGCODE(this->validate();)
904 if (dst == NULL) {
905 dst = (SkPath*)this;
906 }
907
908 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
909 SkPath tmp;
910 tmp.fFillType = fFillType;
911
912 SkPath::Iter iter(*this, false);
913 SkPoint pts[4];
914 SkPath::Verb verb;
915
916 while ((verb = iter.next(pts)) != kDone_Verb) {
917 switch (verb) {
918 case kMove_Verb:
919 tmp.moveTo(pts[0]);
920 break;
921 case kLine_Verb:
922 tmp.lineTo(pts[1]);
923 break;
924 case kQuad_Verb:
925 subdivide_quad_to(&tmp, pts);
926 break;
927 case kCubic_Verb:
928 subdivide_cubic_to(&tmp, pts);
929 break;
930 case kClose_Verb:
931 tmp.close();
932 break;
933 default:
934 SkASSERT(!"unknown verb");
935 break;
936 }
937 }
938
939 dst->swap(tmp);
940 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
941 } else {
942 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +0000943 // fBoundsIsDirty before we set it
944 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +0000946 matrix.mapRect(&dst->fBounds, fBounds);
947 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 } else {
reed@android.comd252db02009-04-01 18:31:44 +0000949 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 }
951
952 if (this != dst) {
953 dst->fVerbs = fVerbs;
954 dst->fPts.setCount(fPts.count());
955 dst->fFillType = fFillType;
956 }
957 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
958 SkDEBUGCODE(dst->validate();)
959 }
960}
961
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962///////////////////////////////////////////////////////////////////////////////
963///////////////////////////////////////////////////////////////////////////////
964
965enum NeedMoveToState {
966 kAfterClose_NeedMoveToState,
967 kAfterCons_NeedMoveToState,
968 kAfterPrefix_NeedMoveToState
969};
970
971SkPath::Iter::Iter() {
972#ifdef SK_DEBUG
973 fPts = NULL;
974 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
975 fForceClose = fNeedMoveTo = fCloseLine = false;
976#endif
977 // need to init enough to make next() harmlessly return kDone_Verb
978 fVerbs = NULL;
979 fVerbStop = NULL;
980 fNeedClose = false;
981}
982
983SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
984 this->setPath(path, forceClose);
985}
986
987void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
988 fPts = path.fPts.begin();
989 fVerbs = path.fVerbs.begin();
990 fVerbStop = path.fVerbs.end();
991 fForceClose = SkToU8(forceClose);
992 fNeedClose = false;
993 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
994}
995
996bool SkPath::Iter::isClosedContour() const {
997 if (fVerbs == NULL || fVerbs == fVerbStop) {
998 return false;
999 }
1000 if (fForceClose) {
1001 return true;
1002 }
1003
1004 const uint8_t* verbs = fVerbs;
1005 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001006
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 if (kMove_Verb == *verbs) {
1008 verbs += 1; // skip the initial moveto
1009 }
1010
1011 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001012 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013 if (kMove_Verb == v) {
1014 break;
1015 }
1016 if (kClose_Verb == v) {
1017 return true;
1018 }
1019 }
1020 return false;
1021}
1022
1023SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1024 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001025 // A special case: if both points are NaN, SkPoint::operation== returns
1026 // false, but the iterator expects that they are treated as the same.
1027 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001028 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1029 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001030 return kClose_Verb;
1031 }
1032
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033 if (pts) {
1034 pts[0] = fLastPt;
1035 pts[1] = fMoveTo;
1036 }
1037 fLastPt = fMoveTo;
1038 fCloseLine = true;
1039 return kLine_Verb;
1040 }
1041 return kClose_Verb;
1042}
1043
1044bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1045 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1046 if (pts) {
1047 *pts = fMoveTo;
1048 }
1049 fNeedClose = fForceClose;
1050 fNeedMoveTo = kAfterCons_NeedMoveToState;
1051 fVerbs -= 1;
1052 return true;
1053 }
1054
1055 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1056 if (pts) {
1057 *pts = fMoveTo;
1058 }
1059 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1060 } else {
1061 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1062 if (pts) {
1063 *pts = fPts[-1];
1064 }
1065 }
1066 return false;
1067}
1068
1069SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1070 if (fVerbs == fVerbStop) {
1071 if (fNeedClose) {
1072 if (kLine_Verb == this->autoClose(pts)) {
1073 return kLine_Verb;
1074 }
1075 fNeedClose = false;
1076 return kClose_Verb;
1077 }
1078 return kDone_Verb;
1079 }
1080
1081 unsigned verb = *fVerbs++;
1082 const SkPoint* srcPts = fPts;
1083
1084 switch (verb) {
1085 case kMove_Verb:
1086 if (fNeedClose) {
1087 fVerbs -= 1;
1088 verb = this->autoClose(pts);
1089 if (verb == kClose_Verb) {
1090 fNeedClose = false;
1091 }
1092 return (Verb)verb;
1093 }
1094 if (fVerbs == fVerbStop) { // might be a trailing moveto
1095 return kDone_Verb;
1096 }
1097 fMoveTo = *srcPts;
1098 if (pts) {
1099 pts[0] = *srcPts;
1100 }
1101 srcPts += 1;
1102 fNeedMoveTo = kAfterCons_NeedMoveToState;
1103 fNeedClose = fForceClose;
1104 break;
1105 case kLine_Verb:
1106 if (this->cons_moveTo(pts)) {
1107 return kMove_Verb;
1108 }
1109 if (pts) {
1110 pts[1] = srcPts[0];
1111 }
1112 fLastPt = srcPts[0];
1113 fCloseLine = false;
1114 srcPts += 1;
1115 break;
1116 case kQuad_Verb:
1117 if (this->cons_moveTo(pts)) {
1118 return kMove_Verb;
1119 }
1120 if (pts) {
1121 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1122 }
1123 fLastPt = srcPts[1];
1124 srcPts += 2;
1125 break;
1126 case kCubic_Verb:
1127 if (this->cons_moveTo(pts)) {
1128 return kMove_Verb;
1129 }
1130 if (pts) {
1131 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1132 }
1133 fLastPt = srcPts[2];
1134 srcPts += 3;
1135 break;
1136 case kClose_Verb:
1137 verb = this->autoClose(pts);
1138 if (verb == kLine_Verb) {
1139 fVerbs -= 1;
1140 } else {
1141 fNeedClose = false;
1142 }
1143 fNeedMoveTo = kAfterClose_NeedMoveToState;
1144 break;
1145 }
1146 fPts = srcPts;
1147 return (Verb)verb;
1148}
1149
1150///////////////////////////////////////////////////////////////////////////////
1151
1152static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist,
1153 int count) {
1154 SkASSERT(dist > 0);
1155
1156 count *= 2;
1157 for (int i = 0; i < count; i++) {
1158 if (SkScalarAbs(p[i] - q[i]) > dist) {
1159 return true;
1160 }
1161 }
1162 return false;
1163}
1164
1165static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist,
1166 int subLevel = 4) {
1167 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) {
1168 SkPoint tmp[5];
1169 SkChopQuadAtHalf(pts, tmp);
1170
1171 subdivide_quad(dst, &tmp[0], dist, subLevel);
1172 subdivide_quad(dst, &tmp[2], dist, subLevel);
1173 } else {
1174 dst->quadTo(pts[1], pts[2]);
1175 }
1176}
1177
1178static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist,
1179 int subLevel = 4) {
1180 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) {
1181 SkPoint tmp[7];
1182 SkChopCubicAtHalf(pts, tmp);
1183
1184 subdivide_cubic(dst, &tmp[0], dist, subLevel);
1185 subdivide_cubic(dst, &tmp[3], dist, subLevel);
1186 } else {
1187 dst->cubicTo(pts[1], pts[2], pts[3]);
1188 }
1189}
1190
1191void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const {
1192 SkPath tmpPath;
1193 if (NULL == dst || this == dst) {
1194 dst = &tmpPath;
1195 }
1196
1197 SkPath::Iter iter(*this, false);
1198 SkPoint pts[4];
1199
1200 for (;;) {
1201 switch (iter.next(pts)) {
1202 case SkPath::kMove_Verb:
1203 dst->moveTo(pts[0]);
1204 break;
1205 case SkPath::kLine_Verb:
1206 if (!bendLines) {
1207 dst->lineTo(pts[1]);
1208 break;
1209 }
1210 // construct a quad from the line
1211 pts[2] = pts[1];
1212 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX),
1213 SkScalarAve(pts[0].fY, pts[2].fY));
1214 // fall through to the quad case
1215 case SkPath::kQuad_Verb:
1216 subdivide_quad(dst, pts, dist);
1217 break;
1218 case SkPath::kCubic_Verb:
1219 subdivide_cubic(dst, pts, dist);
1220 break;
1221 case SkPath::kClose_Verb:
1222 dst->close();
1223 break;
1224 case SkPath::kDone_Verb:
1225 goto DONE;
1226 }
1227 }
1228DONE:
1229 if (&tmpPath == dst) { // i.e. the dst should be us
1230 dst->swap(*(SkPath*)this);
1231 }
1232}
1233
1234///////////////////////////////////////////////////////////////////////
1235/*
1236 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1237*/
1238
1239void SkPath::flatten(SkFlattenableWriteBuffer& buffer) const {
1240 SkDEBUGCODE(this->validate();)
1241
1242 buffer.write32(fPts.count());
1243 buffer.write32(fVerbs.count());
1244 buffer.write32(fFillType);
1245 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1246 buffer.writePad(fVerbs.begin(), fVerbs.count());
1247}
1248
1249void SkPath::unflatten(SkFlattenableReadBuffer& buffer) {
1250 fPts.setCount(buffer.readS32());
1251 fVerbs.setCount(buffer.readS32());
1252 fFillType = buffer.readS32();
1253 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1254 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001255
reed@android.comd252db02009-04-01 18:31:44 +00001256 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257
1258 SkDEBUGCODE(this->validate();)
1259}
1260
1261///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262///////////////////////////////////////////////////////////////////////////////
1263
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264void SkPath::dump(bool forceClose, const char title[]) const {
1265 Iter iter(*this, forceClose);
1266 SkPoint pts[4];
1267 Verb verb;
1268
1269 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1270 title ? title : "");
1271
1272 while ((verb = iter.next(pts)) != kDone_Verb) {
1273 switch (verb) {
1274 case kMove_Verb:
1275#ifdef SK_CAN_USE_FLOAT
1276 SkDebugf(" path: moveTo [%g %g]\n",
1277 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1278#else
1279 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1280#endif
1281 break;
1282 case kLine_Verb:
1283#ifdef SK_CAN_USE_FLOAT
1284 SkDebugf(" path: lineTo [%g %g]\n",
1285 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1286#else
1287 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1288#endif
1289 break;
1290 case kQuad_Verb:
1291#ifdef SK_CAN_USE_FLOAT
1292 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1293 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1294 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1295#else
1296 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1297 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1298#endif
1299 break;
1300 case kCubic_Verb:
1301#ifdef SK_CAN_USE_FLOAT
1302 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1303 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1304 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1305 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1306#else
1307 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1308 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1309 pts[3].fX, pts[3].fY);
1310#endif
1311 break;
1312 case kClose_Verb:
1313 SkDebugf(" path: close\n");
1314 break;
1315 default:
1316 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1317 verb = kDone_Verb; // stop the loop
1318 break;
1319 }
1320 }
1321 SkDebugf("path: done %s\n", title ? title : "");
1322}
1323
reed@android.come522ca52009-11-23 20:10:41 +00001324void SkPath::dump() const {
1325 this->dump(false);
1326}
1327
1328#ifdef SK_DEBUG
1329void SkPath::validate() const {
1330 SkASSERT(this != NULL);
1331 SkASSERT((fFillType & ~3) == 0);
1332 fPts.validate();
1333 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001334
reed@android.come522ca52009-11-23 20:10:41 +00001335 if (!fBoundsIsDirty) {
1336 SkRect bounds;
1337 compute_pt_bounds(&bounds, fPts);
1338 if (fPts.count() <= 1) {
1339 // if we're empty, fBounds may be empty but translated, so we can't
1340 // necessarily compare to bounds directly
1341 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1342 // be [2, 2, 2, 2]
1343 SkASSERT(bounds.isEmpty());
1344 SkASSERT(fBounds.isEmpty());
1345 } else {
1346 fBounds.contains(bounds);
1347 }
1348 }
1349}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350#endif
reed@android.come522ca52009-11-23 20:10:41 +00001351