blob: 97b366d6899331a40ec3f7cfa333ab8cbc9151df [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**
5** 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
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** 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
15** 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).
27
28 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,
30 avoiding the need to revisit all of the points in computeBounds().
31 */
32class SkAutoPathBoundsUpdate {
33public:
34 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
35 this->init(path);
36 }
37
38 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
39 SkScalar right, SkScalar bottom) {
40 fRect.set(left, top, right, bottom);
41 this->init(path);
42 }
43
44 ~SkAutoPathBoundsUpdate() {
45 if (fEmpty) {
46 fPath->fFastBounds = fRect;
47 fPath->fFastBoundsIsDirty = false;
48 } else if (!fDirty) {
49 fPath->fFastBounds.join(fRect);
50 fPath->fFastBoundsIsDirty = false;
51 }
52 }
53
54private:
55 const SkPath* fPath;
56 SkRect fRect;
57 bool fDirty;
58 bool fEmpty;
59
60 // returns true if we should proceed
61 void init(const SkPath* path) {
62 fPath = path;
63 fDirty = path->fFastBoundsIsDirty;
64 fEmpty = path->isEmpty();
reed@android.com49f0ff22009-03-19 21:52:42 +000065 // can't use fRect for our bounds unless we know is upright and proper
66 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 }
68};
69
70static void compute_fast_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
71 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
72 bounds->set(0, 0, 0, 0);
73 } else {
74 bounds->set(pts.begin(), pts.count());
75// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
76 }
77}
78
79////////////////////////////////////////////////////////////////////////////
80
81/*
82 Stores the verbs and points as they are given to us, with exceptions:
83 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
84 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
85
86 The iterator does more cleanup, especially if forceClose == true
87 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
88 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
89 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
90*/
91
92////////////////////////////////////////////////////////////////////////////
93
94SkPath::SkPath() : fFastBoundsIsDirty(true), fFillType(kWinding_FillType) {}
95
96SkPath::SkPath(const SkPath& src) {
97 SkDEBUGCODE(src.validate();)
98 *this = src;
99}
100
101SkPath::~SkPath() {
102 SkDEBUGCODE(this->validate();)
103}
104
105SkPath& SkPath::operator=(const SkPath& src) {
106 SkDEBUGCODE(src.validate();)
107
108 if (this != &src) {
109 fFastBounds = src.fFastBounds;
110 fPts = src.fPts;
111 fVerbs = src.fVerbs;
112 fFillType = src.fFillType;
113 fFastBoundsIsDirty = src.fFastBoundsIsDirty;
114 }
115 SkDEBUGCODE(this->validate();)
116 return *this;
117}
118
reed@android.com3abec1d2009-03-02 05:36:20 +0000119bool operator==(const SkPath& a, const SkPath& b) {
120 return &a == &b ||
121 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts);
122}
123
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124void SkPath::swap(SkPath& other) {
125 SkASSERT(&other != NULL);
126
127 if (this != &other) {
128 SkTSwap<SkRect>(fFastBounds, other.fFastBounds);
129 fPts.swap(other.fPts);
130 fVerbs.swap(other.fVerbs);
131 SkTSwap<uint8_t>(fFillType, other.fFillType);
132 SkTSwap<uint8_t>(fFastBoundsIsDirty, other.fFastBoundsIsDirty);
133 }
134}
135
136void SkPath::reset() {
137 SkDEBUGCODE(this->validate();)
138
139 fPts.reset();
140 fVerbs.reset();
141 fFastBoundsIsDirty = true;
142}
143
144void SkPath::rewind() {
145 SkDEBUGCODE(this->validate();)
146
147 fPts.rewind();
148 fVerbs.rewind();
149 fFastBoundsIsDirty = true;
150}
151
152bool SkPath::isEmpty() const {
153 SkDEBUGCODE(this->validate();)
154
155 int count = fVerbs.count();
156 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
157}
158
159bool SkPath::isRect(SkRect*) const {
160 SkDEBUGCODE(this->validate();)
reed@android.com3abec1d2009-03-02 05:36:20 +0000161
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 SkASSERT(!"unimplemented");
163 return false;
164}
165
166int SkPath::getPoints(SkPoint copy[], int max) const {
167 SkDEBUGCODE(this->validate();)
168
169 SkASSERT(max >= 0);
170 int count = fPts.count();
171 if (copy && max > 0 && count > 0) {
172 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
173 }
174 return count;
175}
176
177void SkPath::getLastPt(SkPoint* lastPt) const {
178 SkDEBUGCODE(this->validate();)
179
180 if (lastPt) {
181 int count = fPts.count();
182 if (count == 0) {
183 lastPt->set(0, 0);
184 } else {
185 *lastPt = fPts[count - 1];
186 }
187 }
188}
189
190void SkPath::setLastPt(SkScalar x, SkScalar y) {
191 SkDEBUGCODE(this->validate();)
192
193 int count = fPts.count();
194 if (count == 0) {
195 this->moveTo(x, y);
196 } else {
197 fPts[count - 1].set(x, y);
198 }
199}
200
201#define ALWAYS_FAST_BOUNDS_FOR_NOW true
202
203void SkPath::computeBounds(SkRect* bounds, BoundsType bt) const {
204 SkDEBUGCODE(this->validate();)
205
206 SkASSERT(bounds);
207
208 // we BoundsType for now
209
210 if (fFastBoundsIsDirty) {
211 fFastBoundsIsDirty = false;
212 compute_fast_bounds(&fFastBounds, fPts);
213 }
214 *bounds = fFastBounds;
215}
216
217//////////////////////////////////////////////////////////////////////////////
218// Construction methods
219
220void SkPath::incReserve(U16CPU inc) {
221 SkDEBUGCODE(this->validate();)
222
223 fVerbs.setReserve(fVerbs.count() + inc);
224 fPts.setReserve(fPts.count() + inc);
225
226 SkDEBUGCODE(this->validate();)
227}
228
229void SkPath::moveTo(SkScalar x, SkScalar y) {
230 SkDEBUGCODE(this->validate();)
231
232 int vc = fVerbs.count();
233 SkPoint* pt;
234
235 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
236 pt = &fPts[fPts.count() - 1];
237 } else {
238 pt = fPts.append();
239 *fVerbs.append() = kMove_Verb;
240 }
241 pt->set(x, y);
242
243 fFastBoundsIsDirty = true;
244}
245
246void SkPath::rMoveTo(SkScalar x, SkScalar y) {
247 SkPoint pt;
248 this->getLastPt(&pt);
249 this->moveTo(pt.fX + x, pt.fY + y);
250}
251
252void SkPath::lineTo(SkScalar x, SkScalar y) {
253 SkDEBUGCODE(this->validate();)
254
255 if (fVerbs.count() == 0) {
256 fPts.append()->set(0, 0);
257 *fVerbs.append() = kMove_Verb;
258 }
259 fPts.append()->set(x, y);
260 *fVerbs.append() = kLine_Verb;
261
262 fFastBoundsIsDirty = true;
263}
264
265void SkPath::rLineTo(SkScalar x, SkScalar y) {
266 SkPoint pt;
267 this->getLastPt(&pt);
268 this->lineTo(pt.fX + x, pt.fY + y);
269}
270
271void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
272 SkDEBUGCODE(this->validate();)
273
274 if (fVerbs.count() == 0) {
275 fPts.append()->set(0, 0);
276 *fVerbs.append() = kMove_Verb;
277 }
278
279 SkPoint* pts = fPts.append(2);
280 pts[0].set(x1, y1);
281 pts[1].set(x2, y2);
282 *fVerbs.append() = kQuad_Verb;
283
284 fFastBoundsIsDirty = true;
285}
286
287void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
288 SkPoint pt;
289 this->getLastPt(&pt);
290 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
291}
292
293void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
294 SkScalar x3, SkScalar y3) {
295 SkDEBUGCODE(this->validate();)
296
297 if (fVerbs.count() == 0) {
298 fPts.append()->set(0, 0);
299 *fVerbs.append() = kMove_Verb;
300 }
301 SkPoint* pts = fPts.append(3);
302 pts[0].set(x1, y1);
303 pts[1].set(x2, y2);
304 pts[2].set(x3, y3);
305 *fVerbs.append() = kCubic_Verb;
306
307 fFastBoundsIsDirty = true;
308}
309
310void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
311 SkScalar x3, SkScalar y3) {
312 SkPoint pt;
313 this->getLastPt(&pt);
314 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
315 pt.fX + x3, pt.fY + y3);
316}
317
318void SkPath::close() {
319 SkDEBUGCODE(this->validate();)
320
321 int count = fVerbs.count();
322 if (count > 0) {
323 switch (fVerbs[count - 1]) {
324 case kLine_Verb:
325 case kQuad_Verb:
326 case kCubic_Verb:
327 *fVerbs.append() = kClose_Verb;
328 break;
329 default:
330 // don't add a close if the prev wasn't a primitive
331 break;
332 }
333 }
334}
335
336///////////////////////////////////////////////////////////////////////////////
337
338void SkPath::addRect(const SkRect& rect, Direction dir) {
339 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
340}
341
342void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
343 SkScalar bottom, Direction dir) {
344 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
345
346 this->incReserve(5);
347
348 this->moveTo(left, top);
349 if (dir == kCCW_Direction) {
350 this->lineTo(left, bottom);
351 this->lineTo(right, bottom);
352 this->lineTo(right, top);
353 } else {
354 this->lineTo(right, top);
355 this->lineTo(right, bottom);
356 this->lineTo(left, bottom);
357 }
358 this->close();
359}
360
361#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
362
363void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
364 Direction dir) {
365 SkAutoPathBoundsUpdate apbu(this, rect);
366
367 SkScalar w = rect.width();
368 SkScalar halfW = SkScalarHalf(w);
369 SkScalar h = rect.height();
370 SkScalar halfH = SkScalarHalf(h);
371
372 if (halfW <= 0 || halfH <= 0) {
373 return;
374 }
375
376 bool skip_hori = rx >= halfW;
377 bool skip_vert = ry >= halfH;
378
379 if (skip_hori && skip_vert) {
380 this->addOval(rect, dir);
381 return;
382 }
383 if (skip_hori) {
384 rx = halfW;
385 } else if (skip_vert) {
386 ry = halfH;
387 }
388
389 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
390 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
391
392 this->incReserve(17);
393 this->moveTo(rect.fRight - rx, rect.fTop);
394 if (dir == kCCW_Direction) {
395 if (!skip_hori) {
396 this->lineTo(rect.fLeft + rx, rect.fTop); // top
397 }
398 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
399 rect.fLeft, rect.fTop + ry - sy,
400 rect.fLeft, rect.fTop + ry); // top-left
401 if (!skip_vert) {
402 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
403 }
404 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
405 rect.fLeft + rx - sx, rect.fBottom,
406 rect.fLeft + rx, rect.fBottom); // bot-left
407 if (!skip_hori) {
408 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
409 }
410 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
411 rect.fRight, rect.fBottom - ry + sy,
412 rect.fRight, rect.fBottom - ry); // bot-right
413 if (!skip_vert) {
414 this->lineTo(rect.fRight, rect.fTop + ry);
415 }
416 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
417 rect.fRight - rx + sx, rect.fTop,
418 rect.fRight - rx, rect.fTop); // top-right
419 } else {
420 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
421 rect.fRight, rect.fTop + ry - sy,
422 rect.fRight, rect.fTop + ry); // top-right
423 if (!skip_vert) {
424 this->lineTo(rect.fRight, rect.fBottom - ry);
425 }
426 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
427 rect.fRight - rx + sx, rect.fBottom,
428 rect.fRight - rx, rect.fBottom); // bot-right
429 if (!skip_hori) {
430 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
431 }
432 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
433 rect.fLeft, rect.fBottom - ry + sy,
434 rect.fLeft, rect.fBottom - ry); // bot-left
435 if (!skip_vert) {
436 this->lineTo(rect.fLeft, rect.fTop + ry); // left
437 }
438 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
439 rect.fLeft + rx - sx, rect.fTop,
440 rect.fLeft + rx, rect.fTop); // top-left
441 if (!skip_hori) {
442 this->lineTo(rect.fRight - rx, rect.fTop); // top
443 }
444 }
445 this->close();
446}
447
448static void add_corner_arc(SkPath* path, const SkRect& rect,
449 SkScalar rx, SkScalar ry, int startAngle,
450 SkPath::Direction dir, bool forceMoveTo) {
451 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
452 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
453
454 SkRect r;
455 r.set(-rx, -ry, rx, ry);
456
457 switch (startAngle) {
458 case 0:
459 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
460 break;
461 case 90:
462 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
463 break;
464 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
465 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
466 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
467 }
468
469 SkScalar start = SkIntToScalar(startAngle);
470 SkScalar sweep = SkIntToScalar(90);
471 if (SkPath::kCCW_Direction == dir) {
472 start += sweep;
473 sweep = -sweep;
474 }
475
476 path->arcTo(r, start, sweep, forceMoveTo);
477}
478
479void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
480 Direction dir) {
481 SkAutoPathBoundsUpdate apbu(this, rect);
482
483 if (kCW_Direction == dir) {
484 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
485 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
486 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
487 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
488 } else {
489 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
490 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
491 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
492 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
493 }
494 this->close();
495}
496
497void SkPath::addOval(const SkRect& oval, Direction dir) {
498 SkAutoPathBoundsUpdate apbu(this, oval);
499
500 SkScalar cx = oval.centerX();
501 SkScalar cy = oval.centerY();
502 SkScalar rx = SkScalarHalf(oval.width());
503 SkScalar ry = SkScalarHalf(oval.height());
504#if 0 // these seem faster than using quads (1/2 the number of edges)
505 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
506 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
507
508 this->incReserve(13);
509 this->moveTo(cx + rx, cy);
510 if (dir == kCCW_Direction) {
511 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
512 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
513 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
514 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
515 } else {
516 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
517 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
518 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
519 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
520 }
521#else
522 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
523 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
524 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
525 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
526
527 /*
528 To handle imprecision in computing the center and radii, we revert to
529 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
530 to ensure that we don't exceed the oval's bounds *ever*, since we want
531 to use oval for our fast-bounds, rather than have to recompute it.
532 */
533 const SkScalar L = oval.fLeft; // cx - rx
534 const SkScalar T = oval.fTop; // cy - ry
535 const SkScalar R = oval.fRight; // cx + rx
536 const SkScalar B = oval.fBottom; // cy + ry
537
538 this->incReserve(17); // 8 quads + close
539 this->moveTo(R, cy);
540 if (dir == kCCW_Direction) {
541 this->quadTo( R, cy - sy, cx + mx, cy - my);
542 this->quadTo(cx + sx, T, cx , T);
543 this->quadTo(cx - sx, T, cx - mx, cy - my);
544 this->quadTo( L, cy - sy, L, cy );
545 this->quadTo( L, cy + sy, cx - mx, cy + my);
546 this->quadTo(cx - sx, B, cx , B);
547 this->quadTo(cx + sx, B, cx + mx, cy + my);
548 this->quadTo( R, cy + sy, R, cy );
549 } else {
550 this->quadTo( R, cy + sy, cx + mx, cy + my);
551 this->quadTo(cx + sx, B, cx , B);
552 this->quadTo(cx - sx, B, cx - mx, cy + my);
553 this->quadTo( L, cy + sy, L, cy );
554 this->quadTo( L, cy - sy, cx - mx, cy - my);
555 this->quadTo(cx - sx, T, cx , T);
556 this->quadTo(cx + sx, T, cx + mx, cy - my);
557 this->quadTo( R, cy - sy, R, cy );
558 }
559#endif
560 this->close();
561}
562
563void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
564 if (r > 0) {
565 SkRect rect;
566 rect.set(x - r, y - r, x + r, y + r);
567 this->addOval(rect, dir);
568 }
569}
570
571#include "SkGeometry.h"
572
573static int build_arc_points(const SkRect& oval, SkScalar startAngle,
574 SkScalar sweepAngle,
575 SkPoint pts[kSkBuildQuadArcStorage]) {
576 SkVector start, stop;
577
578 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
579 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
580 &stop.fX);
581
582 SkMatrix matrix;
583
584 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
585 matrix.postTranslate(oval.centerX(), oval.centerY());
586
587 return SkBuildQuadArc(start, stop,
588 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
589 &matrix, pts);
590}
591
592void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
593 bool forceMoveTo) {
594 if (oval.width() < 0 || oval.height() < 0) {
595 return;
596 }
597
598 SkPoint pts[kSkBuildQuadArcStorage];
599 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
600 SkASSERT((count & 1) == 1);
601
602 if (fVerbs.count() == 0) {
603 forceMoveTo = true;
604 }
605 this->incReserve(count);
606 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
607 for (int i = 1; i < count; i += 2) {
608 this->quadTo(pts[i], pts[i+1]);
609 }
610}
611
612void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
613 SkScalar sweepAngle) {
614 if (oval.isEmpty() || 0 == sweepAngle) {
615 return;
616 }
617
618 const SkScalar kFullCircleAngle = SkIntToScalar(360);
619
620 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
621 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
622 return;
623 }
624
625 SkPoint pts[kSkBuildQuadArcStorage];
626 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
627
628 this->incReserve(count);
629 this->moveTo(pts[0]);
630 for (int i = 1; i < count; i += 2) {
631 this->quadTo(pts[i], pts[i+1]);
632 }
633}
634
635/*
636 Need to handle the case when the angle is sharp, and our computed end-points
637 for the arc go behind pt1 and/or p2...
638*/
639void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
640 SkScalar radius) {
641 SkVector before, after;
642
643 // need to know our prev pt so we can construct tangent vectors
644 {
645 SkPoint start;
646 this->getLastPt(&start);
647 before.setNormalize(x1 - start.fX, y1 - start.fY);
648 after.setNormalize(x2 - x1, y2 - y1);
649 }
650
651 SkScalar cosh = SkPoint::DotProduct(before, after);
652 SkScalar sinh = SkPoint::CrossProduct(before, after);
653
654 if (SkScalarNearlyZero(sinh)) { // angle is too tight
655 return;
656 }
657
658 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
659 if (dist < 0) {
660 dist = -dist;
661 }
662
663 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
664 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
665 SkRotationDirection arcDir;
666
667 // now turn before/after into normals
668 if (sinh > 0) {
669 before.rotateCCW();
670 after.rotateCCW();
671 arcDir = kCW_SkRotationDirection;
672 } else {
673 before.rotateCW();
674 after.rotateCW();
675 arcDir = kCCW_SkRotationDirection;
676 }
677
678 SkMatrix matrix;
679 SkPoint pts[kSkBuildQuadArcStorage];
680
681 matrix.setScale(radius, radius);
682 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
683 yy - SkScalarMul(radius, before.fY));
684
685 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
686
687 this->incReserve(count);
688 // [xx,yy] == pts[0]
689 this->lineTo(xx, yy);
690 for (int i = 1; i < count; i += 2) {
691 this->quadTo(pts[i], pts[i+1]);
692 }
693}
694
695///////////////////////////////////////////////////////////////////////////////
696
697void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
698 SkMatrix matrix;
699
700 matrix.setTranslate(dx, dy);
701 this->addPath(path, matrix);
702}
703
704void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
705 this->incReserve(path.fPts.count());
706
707 Iter iter(path, false);
708 SkPoint pts[4];
709 Verb verb;
710
711 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
712
713 while ((verb = iter.next(pts)) != kDone_Verb) {
714 switch (verb) {
715 case kMove_Verb:
716 proc(matrix, &pts[0], &pts[0], 1);
717 this->moveTo(pts[0]);
718 break;
719 case kLine_Verb:
720 proc(matrix, &pts[1], &pts[1], 1);
721 this->lineTo(pts[1]);
722 break;
723 case kQuad_Verb:
724 proc(matrix, &pts[1], &pts[1], 2);
725 this->quadTo(pts[1], pts[2]);
726 break;
727 case kCubic_Verb:
728 proc(matrix, &pts[1], &pts[1], 3);
729 this->cubicTo(pts[1], pts[2], pts[3]);
730 break;
731 case kClose_Verb:
732 this->close();
733 break;
734 default:
735 SkASSERT(!"unknown verb");
736 }
737 }
738}
739
740///////////////////////////////////////////////////////////////////////////////
741
742static const uint8_t gPtsInVerb[] = {
743 1, // kMove
744 1, // kLine
745 2, // kQuad
746 3, // kCubic
747 0, // kClose
748 0 // kDone
749};
750
751// ignore the initial moveto, and stop when the 1st contour ends
752void SkPath::pathTo(const SkPath& path) {
753 int i, vcount = path.fVerbs.count();
754 if (vcount == 0) {
755 return;
756 }
757
758 this->incReserve(vcount);
759
760 const uint8_t* verbs = path.fVerbs.begin();
761 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
762
763 SkASSERT(verbs[0] == kMove_Verb);
764 for (i = 1; i < vcount; i++) {
765 switch (verbs[i]) {
766 case kLine_Verb:
767 this->lineTo(pts[0].fX, pts[0].fY);
768 break;
769 case kQuad_Verb:
770 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
771 break;
772 case kCubic_Verb:
773 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
774 pts[2].fX, pts[2].fY);
775 break;
776 case kClose_Verb:
777 return;
778 }
779 pts += gPtsInVerb[verbs[i]];
780 }
781}
782
783// ignore the last point of the 1st contour
784void SkPath::reversePathTo(const SkPath& path) {
785 int i, vcount = path.fVerbs.count();
786 if (vcount == 0) {
787 return;
788 }
789
790 this->incReserve(vcount);
791
792 const uint8_t* verbs = path.fVerbs.begin();
793 const SkPoint* pts = path.fPts.begin();
794
795 SkASSERT(verbs[0] == kMove_Verb);
796 for (i = 1; i < vcount; i++) {
797 int n = gPtsInVerb[verbs[i]];
798 if (n == 0) {
799 break;
800 }
801 pts += n;
802 }
803
804 while (--i > 0) {
805 switch (verbs[i]) {
806 case kLine_Verb:
807 this->lineTo(pts[-1].fX, pts[-1].fY);
808 break;
809 case kQuad_Verb:
810 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
811 break;
812 case kCubic_Verb:
813 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
814 pts[-3].fX, pts[-3].fY);
815 break;
816 default:
817 SkASSERT(!"bad verb");
818 break;
819 }
820 pts -= gPtsInVerb[verbs[i]];
821 }
822}
823
824///////////////////////////////////////////////////////////////////////////////
825
826void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
827 SkMatrix matrix;
828
829 matrix.setTranslate(dx, dy);
830 this->transform(matrix, dst);
831}
832
833#include "SkGeometry.h"
834
835static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
836 int level = 2) {
837 if (--level >= 0) {
838 SkPoint tmp[5];
839
840 SkChopQuadAtHalf(pts, tmp);
841 subdivide_quad_to(path, &tmp[0], level);
842 subdivide_quad_to(path, &tmp[2], level);
843 } else {
844 path->quadTo(pts[1], pts[2]);
845 }
846}
847
848static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
849 int level = 2) {
850 if (--level >= 0) {
851 SkPoint tmp[7];
852
853 SkChopCubicAtHalf(pts, tmp);
854 subdivide_cubic_to(path, &tmp[0], level);
855 subdivide_cubic_to(path, &tmp[3], level);
856 } else {
857 path->cubicTo(pts[1], pts[2], pts[3]);
858 }
859}
860
861void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
862 SkDEBUGCODE(this->validate();)
863 if (dst == NULL) {
864 dst = (SkPath*)this;
865 }
866
867 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
868 SkPath tmp;
869 tmp.fFillType = fFillType;
870
871 SkPath::Iter iter(*this, false);
872 SkPoint pts[4];
873 SkPath::Verb verb;
874
875 while ((verb = iter.next(pts)) != kDone_Verb) {
876 switch (verb) {
877 case kMove_Verb:
878 tmp.moveTo(pts[0]);
879 break;
880 case kLine_Verb:
881 tmp.lineTo(pts[1]);
882 break;
883 case kQuad_Verb:
884 subdivide_quad_to(&tmp, pts);
885 break;
886 case kCubic_Verb:
887 subdivide_cubic_to(&tmp, pts);
888 break;
889 case kClose_Verb:
890 tmp.close();
891 break;
892 default:
893 SkASSERT(!"unknown verb");
894 break;
895 }
896 }
897
898 dst->swap(tmp);
899 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
900 } else {
901 // remember that dst might == this, so be sure to check
902 // fFastBoundsIsDirty before we set it
903 if (!fFastBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
904 // if we're empty, fastbounds should not be mapped
905 matrix.mapRect(&dst->fFastBounds, fFastBounds);
906 dst->fFastBoundsIsDirty = false;
907 } else {
908 dst->fFastBoundsIsDirty = true;
909 }
910
911 if (this != dst) {
912 dst->fVerbs = fVerbs;
913 dst->fPts.setCount(fPts.count());
914 dst->fFillType = fFillType;
915 }
916 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
917 SkDEBUGCODE(dst->validate();)
918 }
919}
920
921void SkPath::updateBoundsCache() const {
922 if (fFastBoundsIsDirty) {
923 SkRect r;
924 this->computeBounds(&r, kFast_BoundsType);
925 SkASSERT(!fFastBoundsIsDirty);
926 }
927}
928
929///////////////////////////////////////////////////////////////////////////////
930///////////////////////////////////////////////////////////////////////////////
931
932enum NeedMoveToState {
933 kAfterClose_NeedMoveToState,
934 kAfterCons_NeedMoveToState,
935 kAfterPrefix_NeedMoveToState
936};
937
938SkPath::Iter::Iter() {
939#ifdef SK_DEBUG
940 fPts = NULL;
941 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
942 fForceClose = fNeedMoveTo = fCloseLine = false;
943#endif
944 // need to init enough to make next() harmlessly return kDone_Verb
945 fVerbs = NULL;
946 fVerbStop = NULL;
947 fNeedClose = false;
948}
949
950SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
951 this->setPath(path, forceClose);
952}
953
954void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
955 fPts = path.fPts.begin();
956 fVerbs = path.fVerbs.begin();
957 fVerbStop = path.fVerbs.end();
958 fForceClose = SkToU8(forceClose);
959 fNeedClose = false;
960 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
961}
962
963bool SkPath::Iter::isClosedContour() const {
964 if (fVerbs == NULL || fVerbs == fVerbStop) {
965 return false;
966 }
967 if (fForceClose) {
968 return true;
969 }
970
971 const uint8_t* verbs = fVerbs;
972 const uint8_t* stop = fVerbStop;
973
974 if (kMove_Verb == *verbs) {
975 verbs += 1; // skip the initial moveto
976 }
977
978 while (verbs < stop) {
979 unsigned v = *verbs++;
980 if (kMove_Verb == v) {
981 break;
982 }
983 if (kClose_Verb == v) {
984 return true;
985 }
986 }
987 return false;
988}
989
990SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
991 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +0000992 // A special case: if both points are NaN, SkPoint::operation== returns
993 // false, but the iterator expects that they are treated as the same.
994 // (consider SkPoint is a 2-dimension float point).
995 if (SkScalarIsNaN(fLastPt.fX) && SkScalarIsNaN(fLastPt.fY) &&
996 SkScalarIsNaN(fMoveTo.fX) && SkScalarIsNaN(fMoveTo.fY)) {
997 return kClose_Verb;
998 }
999
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 if (pts) {
1001 pts[0] = fLastPt;
1002 pts[1] = fMoveTo;
1003 }
1004 fLastPt = fMoveTo;
1005 fCloseLine = true;
1006 return kLine_Verb;
1007 }
1008 return kClose_Verb;
1009}
1010
1011bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1012 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1013 if (pts) {
1014 *pts = fMoveTo;
1015 }
1016 fNeedClose = fForceClose;
1017 fNeedMoveTo = kAfterCons_NeedMoveToState;
1018 fVerbs -= 1;
1019 return true;
1020 }
1021
1022 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1023 if (pts) {
1024 *pts = fMoveTo;
1025 }
1026 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1027 } else {
1028 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1029 if (pts) {
1030 *pts = fPts[-1];
1031 }
1032 }
1033 return false;
1034}
1035
1036SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1037 if (fVerbs == fVerbStop) {
1038 if (fNeedClose) {
1039 if (kLine_Verb == this->autoClose(pts)) {
1040 return kLine_Verb;
1041 }
1042 fNeedClose = false;
1043 return kClose_Verb;
1044 }
1045 return kDone_Verb;
1046 }
1047
1048 unsigned verb = *fVerbs++;
1049 const SkPoint* srcPts = fPts;
1050
1051 switch (verb) {
1052 case kMove_Verb:
1053 if (fNeedClose) {
1054 fVerbs -= 1;
1055 verb = this->autoClose(pts);
1056 if (verb == kClose_Verb) {
1057 fNeedClose = false;
1058 }
1059 return (Verb)verb;
1060 }
1061 if (fVerbs == fVerbStop) { // might be a trailing moveto
1062 return kDone_Verb;
1063 }
1064 fMoveTo = *srcPts;
1065 if (pts) {
1066 pts[0] = *srcPts;
1067 }
1068 srcPts += 1;
1069 fNeedMoveTo = kAfterCons_NeedMoveToState;
1070 fNeedClose = fForceClose;
1071 break;
1072 case kLine_Verb:
1073 if (this->cons_moveTo(pts)) {
1074 return kMove_Verb;
1075 }
1076 if (pts) {
1077 pts[1] = srcPts[0];
1078 }
1079 fLastPt = srcPts[0];
1080 fCloseLine = false;
1081 srcPts += 1;
1082 break;
1083 case kQuad_Verb:
1084 if (this->cons_moveTo(pts)) {
1085 return kMove_Verb;
1086 }
1087 if (pts) {
1088 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1089 }
1090 fLastPt = srcPts[1];
1091 srcPts += 2;
1092 break;
1093 case kCubic_Verb:
1094 if (this->cons_moveTo(pts)) {
1095 return kMove_Verb;
1096 }
1097 if (pts) {
1098 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1099 }
1100 fLastPt = srcPts[2];
1101 srcPts += 3;
1102 break;
1103 case kClose_Verb:
1104 verb = this->autoClose(pts);
1105 if (verb == kLine_Verb) {
1106 fVerbs -= 1;
1107 } else {
1108 fNeedClose = false;
1109 }
1110 fNeedMoveTo = kAfterClose_NeedMoveToState;
1111 break;
1112 }
1113 fPts = srcPts;
1114 return (Verb)verb;
1115}
1116
1117///////////////////////////////////////////////////////////////////////////////
1118
1119static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist,
1120 int count) {
1121 SkASSERT(dist > 0);
1122
1123 count *= 2;
1124 for (int i = 0; i < count; i++) {
1125 if (SkScalarAbs(p[i] - q[i]) > dist) {
1126 return true;
1127 }
1128 }
1129 return false;
1130}
1131
1132static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist,
1133 int subLevel = 4) {
1134 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) {
1135 SkPoint tmp[5];
1136 SkChopQuadAtHalf(pts, tmp);
1137
1138 subdivide_quad(dst, &tmp[0], dist, subLevel);
1139 subdivide_quad(dst, &tmp[2], dist, subLevel);
1140 } else {
1141 dst->quadTo(pts[1], pts[2]);
1142 }
1143}
1144
1145static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist,
1146 int subLevel = 4) {
1147 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) {
1148 SkPoint tmp[7];
1149 SkChopCubicAtHalf(pts, tmp);
1150
1151 subdivide_cubic(dst, &tmp[0], dist, subLevel);
1152 subdivide_cubic(dst, &tmp[3], dist, subLevel);
1153 } else {
1154 dst->cubicTo(pts[1], pts[2], pts[3]);
1155 }
1156}
1157
1158void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const {
1159 SkPath tmpPath;
1160 if (NULL == dst || this == dst) {
1161 dst = &tmpPath;
1162 }
1163
1164 SkPath::Iter iter(*this, false);
1165 SkPoint pts[4];
1166
1167 for (;;) {
1168 switch (iter.next(pts)) {
1169 case SkPath::kMove_Verb:
1170 dst->moveTo(pts[0]);
1171 break;
1172 case SkPath::kLine_Verb:
1173 if (!bendLines) {
1174 dst->lineTo(pts[1]);
1175 break;
1176 }
1177 // construct a quad from the line
1178 pts[2] = pts[1];
1179 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX),
1180 SkScalarAve(pts[0].fY, pts[2].fY));
1181 // fall through to the quad case
1182 case SkPath::kQuad_Verb:
1183 subdivide_quad(dst, pts, dist);
1184 break;
1185 case SkPath::kCubic_Verb:
1186 subdivide_cubic(dst, pts, dist);
1187 break;
1188 case SkPath::kClose_Verb:
1189 dst->close();
1190 break;
1191 case SkPath::kDone_Verb:
1192 goto DONE;
1193 }
1194 }
1195DONE:
1196 if (&tmpPath == dst) { // i.e. the dst should be us
1197 dst->swap(*(SkPath*)this);
1198 }
1199}
1200
1201///////////////////////////////////////////////////////////////////////
1202/*
1203 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1204*/
1205
1206void SkPath::flatten(SkFlattenableWriteBuffer& buffer) const {
1207 SkDEBUGCODE(this->validate();)
1208
1209 buffer.write32(fPts.count());
1210 buffer.write32(fVerbs.count());
1211 buffer.write32(fFillType);
1212 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1213 buffer.writePad(fVerbs.begin(), fVerbs.count());
1214}
1215
1216void SkPath::unflatten(SkFlattenableReadBuffer& buffer) {
1217 fPts.setCount(buffer.readS32());
1218 fVerbs.setCount(buffer.readS32());
1219 fFillType = buffer.readS32();
1220 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1221 buffer.read(fVerbs.begin(), fVerbs.count());
1222
1223 fFastBoundsIsDirty = true;
1224
1225 SkDEBUGCODE(this->validate();)
1226}
1227
1228///////////////////////////////////////////////////////////////////////////////
1229
1230#include "SkString.h"
1231#include "SkStream.h"
1232
1233static void write_scalar(SkWStream* stream, SkScalar value) {
1234 char buffer[SkStrAppendScalar_MaxSize];
1235 char* stop = SkStrAppendScalar(buffer, value);
1236 stream->write(buffer, stop - buffer);
1237}
1238
1239static void append_scalars(SkWStream* stream, char verb, const SkScalar data[],
1240 int count) {
1241 stream->write(&verb, 1);
1242 write_scalar(stream, data[0]);
1243 for (int i = 1; i < count; i++) {
1244 if (data[i] >= 0) {
1245 // can skip the separater if data[i] is negative
1246 stream->write(" ", 1);
1247 }
1248 write_scalar(stream, data[i]);
1249 }
1250}
1251
1252void SkPath::toString(SkString* str) const {
1253 SkDynamicMemoryWStream stream;
1254
1255 SkPath::Iter iter(*this, false);
1256 SkPoint pts[4];
1257
1258 for (;;) {
1259 switch (iter.next(pts)) {
1260 case SkPath::kMove_Verb:
1261 append_scalars(&stream, 'M', &pts[0].fX, 2);
1262 break;
1263 case SkPath::kLine_Verb:
1264 append_scalars(&stream, 'L', &pts[1].fX, 2);
1265 break;
1266 case SkPath::kQuad_Verb:
1267 append_scalars(&stream, 'Q', &pts[1].fX, 4);
1268 break;
1269 case SkPath::kCubic_Verb:
1270 append_scalars(&stream, 'C', &pts[1].fX, 6);
1271 break;
1272 case SkPath::kClose_Verb:
1273 stream.write("Z", 1);
1274 break;
1275 case SkPath::kDone_Verb:
1276 str->resize(stream.getOffset());
1277 stream.copyTo(str->writable_str());
1278 return;
1279 }
1280 }
1281}
1282
1283///////////////////////////////////////////////////////////////////////////////
1284///////////////////////////////////////////////////////////////////////////////
1285
1286#ifdef SK_DEBUG
1287
1288void SkPath::validate() const {
1289 SkASSERT(this != NULL);
1290 SkASSERT((fFillType & ~3) == 0);
1291 fPts.validate();
1292 fVerbs.validate();
1293
1294 if (!fFastBoundsIsDirty) {
1295 SkRect bounds;
1296 compute_fast_bounds(&bounds, fPts);
1297 // can't call contains(), since it returns false if the rect is empty
1298 SkASSERT(fFastBounds.fLeft <= bounds.fLeft);
1299 SkASSERT(fFastBounds.fTop <= bounds.fTop);
1300 SkASSERT(fFastBounds.fRight >= bounds.fRight);
1301 SkASSERT(fFastBounds.fBottom >= bounds.fBottom);
1302 }
1303}
1304
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305void SkPath::dump(bool forceClose, const char title[]) const {
1306 Iter iter(*this, forceClose);
1307 SkPoint pts[4];
1308 Verb verb;
1309
1310 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1311 title ? title : "");
1312
1313 while ((verb = iter.next(pts)) != kDone_Verb) {
1314 switch (verb) {
1315 case kMove_Verb:
1316#ifdef SK_CAN_USE_FLOAT
1317 SkDebugf(" path: moveTo [%g %g]\n",
1318 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1319#else
1320 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1321#endif
1322 break;
1323 case kLine_Verb:
1324#ifdef SK_CAN_USE_FLOAT
1325 SkDebugf(" path: lineTo [%g %g]\n",
1326 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1327#else
1328 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1329#endif
1330 break;
1331 case kQuad_Verb:
1332#ifdef SK_CAN_USE_FLOAT
1333 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1334 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1335 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1336#else
1337 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1338 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1339#endif
1340 break;
1341 case kCubic_Verb:
1342#ifdef SK_CAN_USE_FLOAT
1343 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1344 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1345 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1346 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1347#else
1348 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1349 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1350 pts[3].fX, pts[3].fY);
1351#endif
1352 break;
1353 case kClose_Verb:
1354 SkDebugf(" path: close\n");
1355 break;
1356 default:
1357 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1358 verb = kDone_Verb; // stop the loop
1359 break;
1360 }
1361 }
1362 SkDebugf("path: done %s\n", title ? title : "");
1363}
1364
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365#endif