blob: 7485c0b4ff06b3e2d05c2dc568c119f582b30cce [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000018////////////////////////////////////////////////////////////////////////////
19
20#if SK_DEBUG_PATH_REF
21
22SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
23
24SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
25: fPathRef(pr)
26, fOwner(owner) {
27 pr->addOwner(owner);
28}
29
30SkPath::PathRefDebugRef::~PathRefDebugRef() {
31 fPathRef->removeOwner(fOwner);
32}
33
34void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
35 bool diff = (ref != fPathRef.get());
36 if (diff && NULL != fPathRef.get()) {
37 fPathRef.get()->removeOwner(fOwner);
38 }
39 fPathRef.reset(ref);
40 if (diff && NULL != fPathRef.get()) {
41 fPathRef.get()->addOwner(fOwner);
42 }
43}
44
45void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
46 if (other->fPathRef.get() != fPathRef.get()) {
47 other->fPathRef->removeOwner(other->fOwner);
48 other->fPathRef->addOwner(fOwner);
49
50 fPathRef->removeOwner(fOwner);
51 fPathRef->addOwner(other->fOwner);
52 }
53
54 fPathRef.swap(&other->fPathRef);
55}
56
57SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
58
59SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
60 return fPathRef.operator->();
61}
62
63SkPath::PathRefDebugRef::operator SkPathRef*() {
64 return fPathRef.operator SkPathRef *();
65}
66
67#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000068
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000069////////////////////////////////////////////////////////////////////////////
70
71
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000072SK_DEFINE_INST_COUNT(SkPath);
73
reed@google.com744faba2012-05-29 19:54:52 +000074// This value is just made-up for now. When count is 4, calling memset was much
75// slower than just writing the loop. This seems odd, and hopefully in the
76// future this we appear to have been a fluke...
77#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
78
reed@android.com8a1c16f2008-12-17 15:59:43 +000079////////////////////////////////////////////////////////////////////////////
80
reed@google.com3563c9e2011-11-14 19:34:57 +000081/**
82 * Path.bounds is defined to be the bounds of all the control points.
83 * If we called bounds.join(r) we would skip r if r was empty, which breaks
84 * our promise. Hence we have a custom joiner that doesn't look at emptiness
85 */
86static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
87 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
88 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
89 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
90 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
91}
92
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000093static bool is_degenerate(const SkPath& path) {
94 SkPath::Iter iter(path, false);
95 SkPoint pts[4];
96 return SkPath::kDone_Verb == iter.next(pts);
97}
98
bsalomon@google.com6aa29652012-04-18 13:29:52 +000099class SkAutoDisableOvalCheck {
100public:
101 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
102 fSaved = fPath->fIsOval;
103 }
104
105 ~SkAutoDisableOvalCheck() {
106 fPath->fIsOval = fSaved;
107 }
108
109private:
110 SkPath* fPath;
111 bool fSaved;
112};
113
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000114class SkAutoDisableDirectionCheck {
115public:
116 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
117 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
118 }
119
120 ~SkAutoDisableDirectionCheck() {
121 fPath->fDirection = fSaved;
122 }
123
124private:
125 SkPath* fPath;
126 SkPath::Direction fSaved;
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129/* This guy's constructor/destructor bracket a path editing operation. It is
130 used when we know the bounds of the amount we are going to add to the path
131 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 It captures some state about the path up front (i.e. if it already has a
134 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000135 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000136
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000137 It also notes if the path was originally degenerate, and if so, sets
138 isConvex to true. Thus it can only be used if the contour being added is
139 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 */
141class SkAutoPathBoundsUpdate {
142public:
143 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
144 this->init(path);
145 }
146
147 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
148 SkScalar right, SkScalar bottom) {
149 fRect.set(left, top, right, bottom);
150 this->init(path);
151 }
reed@google.comabf15c12011-01-18 20:35:51 +0000152
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000154 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000156 fPath->fBounds = fRect;
157 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000158 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000160 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000161 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000162 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 }
164 }
reed@google.comabf15c12011-01-18 20:35:51 +0000165
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000167 SkPath* fPath;
168 SkRect fRect;
169 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000170 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000171 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000174 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000176 // Mark the path's bounds as dirty if (1) they are, or (2) the path
177 // is non-finite, and therefore its bounds are not meaningful
178 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000179 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000181 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000182 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 }
184};
185
reed@google.com0bb18bb2012-07-26 15:20:36 +0000186// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000187static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
188 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000189 if (count <= 1) { // we ignore just 1 point (moveto)
190 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000191 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000193 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 }
195}
196
197////////////////////////////////////////////////////////////////////////////
198
199/*
200 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000201 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
203
204 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000205 1. If we encounter degenerate segments, remove them
206 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
207 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
208 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209*/
210
211////////////////////////////////////////////////////////////////////////////
212
reed@google.comd335d1d2012-01-12 18:17:11 +0000213// flag to require a moveTo if we begin with something else, like lineTo etc.
214#define INITIAL_LASTMOVETOINDEX_VALUE ~0
215
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000216SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000217#if SK_DEBUG_PATH_REF
218 : fPathRef(SkPathRef::CreateEmpty(), this)
219#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000220 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000221#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000222 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000223 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000224 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000225 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000226 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000227 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000228 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000229 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000230#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000231 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000232 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000233#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000234}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000236SkPath::SkPath(const SkPath& src)
237#if SK_DEBUG_PATH_REF
238 : fPathRef(this)
239#endif
240{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000242 src.fPathRef.get()->ref();
243 fPathRef.reset(src.fPathRef.get());
244 fBounds = src.fBounds;
245 fFillType = src.fFillType;
246 fBoundsIsDirty = src.fBoundsIsDirty;
247 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000248 fDirection = src.fDirection;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000249 fIsFinite = src.fIsFinite;
250 fSegmentMask = src.fSegmentMask;
251 fLastMoveToIndex = src.fLastMoveToIndex;
252 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000253#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000254 fGenerationID = src.fGenerationID;
255 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000256#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257}
258
259SkPath::~SkPath() {
260 SkDEBUGCODE(this->validate();)
261}
262
263SkPath& SkPath::operator=(const SkPath& src) {
264 SkDEBUGCODE(src.validate();)
265
266 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000267 src.fPathRef.get()->ref();
268 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000269 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000270 fFillType = src.fFillType;
271 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000272 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000273 fDirection = src.fDirection;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000274 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000275 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000276 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000277 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000278 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 }
280 SkDEBUGCODE(this->validate();)
281 return *this;
282}
283
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000284SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000285 // note: don't need to look at isConvex or bounds, since just comparing the
286 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000287
288 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
289 // since it is only a cache of info in the fVerbs, but its a fast way to
290 // notice a difference
291
reed@android.com3abec1d2009-03-02 05:36:20 +0000292 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000293 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000294 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000295}
296
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297void SkPath::swap(SkPath& other) {
298 SkASSERT(&other != NULL);
299
300 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000301 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000302 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000304 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000305 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000306 SkTSwap<uint8_t>(fDirection, other.fDirection);
reed@google.com10296cc2011-09-21 12:29:05 +0000307 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000308 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000309 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000310 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000311 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 }
313}
314
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315static inline bool check_edge_against_rect(const SkPoint& p0,
316 const SkPoint& p1,
317 const SkRect& rect,
318 SkPath::Direction dir) {
319 const SkPoint* edgeBegin;
320 SkVector v;
321 if (SkPath::kCW_Direction == dir) {
322 v = p1 - p0;
323 edgeBegin = &p0;
324 } else {
325 v = p0 - p1;
326 edgeBegin = &p1;
327 }
328 if (v.fX || v.fY) {
329 // check the cross product of v with the vec from edgeBegin to each rect corner
330 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
331 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
332 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
333 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
334 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
335 return false;
336 }
337 }
338 return true;
339}
340
341bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
342 // This only handles non-degenerate convex paths currently.
343 if (kConvex_Convexity != this->getConvexity()) {
344 return false;
345 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000346
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000347 Direction direction;
348 if (!this->cheapComputeDirection(&direction)) {
349 return false;
350 }
351
352 SkPoint firstPt;
353 SkPoint prevPt;
354 RawIter iter(*this);
355 SkPath::Verb verb;
356 SkPoint pts[4];
357 SkDEBUGCODE(int moveCnt = 0;)
358
359 while ((verb = iter.next(pts)) != kDone_Verb) {
360 int nextPt = -1;
361 switch (verb) {
362 case kMove_Verb:
363 SkASSERT(!moveCnt);
364 SkDEBUGCODE(++moveCnt);
365 firstPt = prevPt = pts[0];
366 break;
367 case kLine_Verb:
368 nextPt = 1;
369 SkASSERT(moveCnt);
370 break;
371 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000372 case kConic_Verb:
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000373 SkASSERT(moveCnt);
374 nextPt = 2;
375 break;
376 case kCubic_Verb:
377 SkASSERT(moveCnt);
378 nextPt = 3;
379 break;
380 case kClose_Verb:
381 break;
382 default:
383 SkDEBUGFAIL("unknown verb");
384 }
385 if (-1 != nextPt) {
386 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
387 return false;
388 }
389 prevPt = pts[nextPt];
390 }
391 }
392
393 return check_edge_against_rect(prevPt, firstPt, rect, direction);
394}
395
djsollen@google.com56c69772011-11-08 19:00:26 +0000396#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000397uint32_t SkPath::getGenerationID() const {
398 return fGenerationID;
399}
djsollen@google.come63793a2012-03-21 15:39:03 +0000400
401const SkPath* SkPath::getSourcePath() const {
402 return fSourcePath;
403}
404
405void SkPath::setSourcePath(const SkPath* path) {
406 fSourcePath = path;
407}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000408#endif
409
reed@android.com8a1c16f2008-12-17 15:59:43 +0000410void SkPath::reset() {
411 SkDEBUGCODE(this->validate();)
412
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000413 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000414 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000415 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000416 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000417 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000418 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000419 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000420 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421}
422
423void SkPath::rewind() {
424 SkDEBUGCODE(this->validate();)
425
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000426 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000427 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000428 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000429 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000430 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000431 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000432 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433}
434
435bool SkPath::isEmpty() const {
436 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000437 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438}
439
reed@google.com7e6c4d12012-05-10 14:05:43 +0000440bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000441 int verbCount = fPathRef->countVerbs();
442 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000443
reed@google.com7e6c4d12012-05-10 14:05:43 +0000444 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000445 if (kMove_Verb == fPathRef->atVerb(0) &&
446 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000447 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000448 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000449 line[0] = pts[0];
450 line[1] = pts[1];
451 }
452 return true;
453 }
454 }
455 return false;
456}
457
caryclark@google.comf1316942011-07-26 19:54:45 +0000458/*
459 Determines if path is a rect by keeping track of changes in direction
460 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000461
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 The direction is computed such that:
463 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000464 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000465 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000466 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000467
caryclark@google.comf1316942011-07-26 19:54:45 +0000468A rectangle cycles up/right/down/left or up/left/down/right.
469
470The test fails if:
471 The path is closed, and followed by a line.
472 A second move creates a new endpoint.
473 A diagonal line is parsed.
474 There's more than four changes of direction.
475 There's a discontinuity on the line (e.g., a move in the middle)
476 The line reverses direction.
477 The rectangle doesn't complete a cycle.
478 The path contains a quadratic or cubic.
479 The path contains fewer than four points.
480 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000481
caryclark@google.comf1316942011-07-26 19:54:45 +0000482It's OK if the path has:
483 Several colinear line segments composing a rectangle side.
484 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000485
caryclark@google.comf1316942011-07-26 19:54:45 +0000486The direction takes advantage of the corners found since opposite sides
487must travel in opposite directions.
488
489FIXME: Allow colinear quads and cubics to be treated like lines.
490FIXME: If the API passes fill-only, return true if the filled stroke
491 is a rectangle, though the caller failed to close the path.
492 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000493bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
494 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 int corners = 0;
496 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000497 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000498 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000499 first.set(0, 0);
500 last.set(0, 0);
501 int firstDirection = 0;
502 int lastDirection = 0;
503 int nextDirection = 0;
504 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000505 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000506 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
508 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 savePts = pts;
511 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000512 autoClose = true;
513 case kLine_Verb: {
514 SkScalar left = last.fX;
515 SkScalar top = last.fY;
516 SkScalar right = pts->fX;
517 SkScalar bottom = pts->fY;
518 ++pts;
519 if (left != right && top != bottom) {
520 return false; // diagonal
521 }
522 if (left == right && top == bottom) {
523 break; // single point on side OK
524 }
525 nextDirection = (left != right) << 0 |
526 (left < right || top < bottom) << 1;
527 if (0 == corners) {
528 firstDirection = nextDirection;
529 first = last;
530 last = pts[-1];
531 corners = 1;
532 closedOrMoved = false;
533 break;
534 }
535 if (closedOrMoved) {
536 return false; // closed followed by a line
537 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000538 if (autoClose && nextDirection == firstDirection) {
539 break; // colinear with first
540 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000541 closedOrMoved = autoClose;
542 if (lastDirection != nextDirection) {
543 if (++corners > 4) {
544 return false; // too many direction changes
545 }
546 }
547 last = pts[-1];
548 if (lastDirection == nextDirection) {
549 break; // colinear segment
550 }
551 // Possible values for corners are 2, 3, and 4.
552 // When corners == 3, nextDirection opposes firstDirection.
553 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000554 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000555 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
556 if ((directionCycle ^ turn) != nextDirection) {
557 return false; // direction didn't follow cycle
558 }
559 break;
560 }
561 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000562 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000563 case kCubic_Verb:
564 return false; // quadratic, cubic not allowed
565 case kMove_Verb:
566 last = *pts++;
567 closedOrMoved = true;
568 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000569 default:
570 SkASSERT(!"unexpected verb");
571 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000572 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000573 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000574 lastDirection = nextDirection;
575 }
576 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000577 bool result = 4 == corners && (first == last || autoClose);
578 if (savePts) {
579 *ptsPtr = savePts;
580 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000581 if (result && isClosed) {
582 *isClosed = autoClose;
583 }
584 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000585 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000586 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000587 return result;
588}
589
590bool SkPath::isRect(SkRect* rect) const {
591 SkDEBUGCODE(this->validate();)
592 int currVerb = 0;
593 const SkPoint* pts = fPathRef->points();
caryclark@google.comf68154a2012-11-21 15:18:06 +0000594 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
caryclark@google.comf1316942011-07-26 19:54:45 +0000595 if (result && rect) {
596 *rect = getBounds();
597 }
598 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599}
600
caryclark@google.comf68154a2012-11-21 15:18:06 +0000601bool SkPath::isRect(bool* isClosed, Direction* direction) const {
602 SkDEBUGCODE(this->validate();)
603 int currVerb = 0;
604 const SkPoint* pts = fPathRef->points();
605 return isRectContour(false, &currVerb, &pts, isClosed, direction);
606}
607
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000608bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 SkDEBUGCODE(this->validate();)
610 int currVerb = 0;
611 const SkPoint* pts = fPathRef->points();
612 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000613 Direction testDirs[2];
614 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000615 return false;
616 }
617 const SkPoint* last = pts;
618 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000619 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000620 testRects[0].set(first, SkToS32(last - first));
621 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000622 if (testRects[0].contains(testRects[1])) {
623 if (rects) {
624 rects[0] = testRects[0];
625 rects[1] = testRects[1];
626 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000627 if (dirs) {
628 dirs[0] = testDirs[0];
629 dirs[1] = testDirs[1];
630 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000631 return true;
632 }
633 if (testRects[1].contains(testRects[0])) {
634 if (rects) {
635 rects[0] = testRects[1];
636 rects[1] = testRects[0];
637 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000638 if (dirs) {
639 dirs[0] = testDirs[1];
640 dirs[1] = testDirs[0];
641 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000642 return true;
643 }
644 }
645 return false;
646}
647
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648int SkPath::countPoints() const {
649 return fPathRef->countPoints();
650}
651
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000652int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 SkDEBUGCODE(this->validate();)
654
655 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000656 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 int count = SkMin32(max, fPathRef->countPoints());
658 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
659 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660}
661
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000662SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
664 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000665 }
666 return SkPoint::Make(0, 0);
667}
668
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000669int SkPath::countVerbs() const {
670 return fPathRef->countVerbs();
671}
672
673static inline void copy_verbs_reverse(uint8_t* inorderDst,
674 const uint8_t* reversedSrc,
675 int count) {
676 for (int i = 0; i < count; ++i) {
677 inorderDst[i] = reversedSrc[~i];
678 }
679}
680
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000681int SkPath::getVerbs(uint8_t dst[], int max) const {
682 SkDEBUGCODE(this->validate();)
683
684 SkASSERT(max >= 0);
685 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 int count = SkMin32(max, fPathRef->countVerbs());
687 copy_verbs_reverse(dst, fPathRef->verbs(), count);
688 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000689}
690
reed@google.com294dd7b2011-10-11 11:58:32 +0000691bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 SkDEBUGCODE(this->validate();)
693
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000695 if (count > 0) {
696 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000699 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000701 if (lastPt) {
702 lastPt->set(0, 0);
703 }
704 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705}
706
707void SkPath::setLastPt(SkScalar x, SkScalar y) {
708 SkDEBUGCODE(this->validate();)
709
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711 if (count == 0) {
712 this->moveTo(x, y);
713 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000714 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000715 SkPathRef::Editor ed(&fPathRef);
716 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000717 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 }
719}
720
reed@android.comd252db02009-04-01 18:31:44 +0000721void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000723 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000726 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727}
728
reed@google.com04863fa2011-05-15 04:08:24 +0000729void SkPath::setConvexity(Convexity c) {
730 if (fConvexity != c) {
731 fConvexity = c;
732 GEN_ID_INC;
733 }
734}
735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736//////////////////////////////////////////////////////////////////////////////
737// Construction methods
738
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000739#define DIRTY_AFTER_EDIT \
740 do { \
741 fBoundsIsDirty = true; \
742 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000743 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000744 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000745 } while (0)
746
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000747#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
748 do { \
749 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000750 } while (0)
751
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752void SkPath::incReserve(U16CPU inc) {
753 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000754 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755 SkDEBUGCODE(this->validate();)
756}
757
758void SkPath::moveTo(SkScalar x, SkScalar y) {
759 SkDEBUGCODE(this->validate();)
760
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000761 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762
reed@google.comd335d1d2012-01-12 18:17:11 +0000763 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000764 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000765
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000766 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000768 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000769 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770}
771
772void SkPath::rMoveTo(SkScalar x, SkScalar y) {
773 SkPoint pt;
774 this->getLastPt(&pt);
775 this->moveTo(pt.fX + x, pt.fY + y);
776}
777
reed@google.comd335d1d2012-01-12 18:17:11 +0000778void SkPath::injectMoveToIfNeeded() {
779 if (fLastMoveToIndex < 0) {
780 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000781 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000782 x = y = 0;
783 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000784 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000785 x = pt.fX;
786 y = pt.fY;
787 }
788 this->moveTo(x, y);
789 }
790}
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792void SkPath::lineTo(SkScalar x, SkScalar y) {
793 SkDEBUGCODE(this->validate();)
794
reed@google.comd335d1d2012-01-12 18:17:11 +0000795 this->injectMoveToIfNeeded();
796
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 SkPathRef::Editor ed(&fPathRef);
798 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000799 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000801 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000802 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803}
804
805void SkPath::rLineTo(SkScalar x, SkScalar y) {
806 SkPoint pt;
807 this->getLastPt(&pt);
808 this->lineTo(pt.fX + x, pt.fY + y);
809}
810
811void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
812 SkDEBUGCODE(this->validate();)
reed@google.com277c3f82013-05-31 15:17:50 +0000813
reed@google.comd335d1d2012-01-12 18:17:11 +0000814 this->injectMoveToIfNeeded();
reed@google.com277c3f82013-05-31 15:17:50 +0000815
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000816 SkPathRef::Editor ed(&fPathRef);
817 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818 pts[0].set(x1, y1);
819 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000820 fSegmentMask |= kQuad_SegmentMask;
reed@google.com277c3f82013-05-31 15:17:50 +0000821
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000822 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000823 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824}
825
826void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
827 SkPoint pt;
828 this->getLastPt(&pt);
829 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
830}
831
reed@google.com277c3f82013-05-31 15:17:50 +0000832void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
833 SkScalar w) {
834 // check for <= 0 or NaN with this test
835 if (!(w > 0)) {
836 this->lineTo(x2, y2);
837 } else if (!SkScalarIsFinite(w)) {
838 this->lineTo(x1, y1);
839 this->lineTo(x2, y2);
840 } else if (SK_Scalar1 == w) {
841 this->quadTo(x1, y1, x2, y2);
842 } else {
843 SkDEBUGCODE(this->validate();)
844
845 this->injectMoveToIfNeeded();
846
847 SkPathRef::Editor ed(&fPathRef);
848 SkPoint* pts = ed.growForConic(w);
849 pts[0].set(x1, y1);
850 pts[1].set(x2, y2);
851 fSegmentMask |= kConic_SegmentMask;
852
853 GEN_ID_INC;
854 DIRTY_AFTER_EDIT;
855 }
856}
857
858void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
859 SkScalar w) {
860 SkPoint pt;
861 this->getLastPt(&pt);
862 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
863}
864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
866 SkScalar x3, SkScalar y3) {
867 SkDEBUGCODE(this->validate();)
868
reed@google.comd335d1d2012-01-12 18:17:11 +0000869 this->injectMoveToIfNeeded();
870
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000871 SkPathRef::Editor ed(&fPathRef);
872 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 pts[0].set(x1, y1);
874 pts[1].set(x2, y2);
875 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000876 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000878 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000879 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880}
881
882void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
883 SkScalar x3, SkScalar y3) {
884 SkPoint pt;
885 this->getLastPt(&pt);
886 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
887 pt.fX + x3, pt.fY + y3);
888}
889
890void SkPath::close() {
891 SkDEBUGCODE(this->validate();)
892
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000893 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000895 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 case kLine_Verb:
897 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000898 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000899 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000900 case kMove_Verb: {
901 SkPathRef::Editor ed(&fPathRef);
902 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000903 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000905 }
reed@google.com277c3f82013-05-31 15:17:50 +0000906 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000907 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000908 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000909 default:
910 SkASSERT(!"unexpected verb");
911 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 }
913 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000914
915 // signal that we need a moveTo to follow us (unless we're done)
916#if 0
917 if (fLastMoveToIndex >= 0) {
918 fLastMoveToIndex = ~fLastMoveToIndex;
919 }
920#else
921 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
922#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923}
924
925///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000926
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000927static void assert_known_direction(int dir) {
928 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
929}
930
reed@android.com8a1c16f2008-12-17 15:59:43 +0000931void SkPath::addRect(const SkRect& rect, Direction dir) {
932 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
933}
934
935void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
936 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000937 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000938 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
939 SkAutoDisableDirectionCheck addc(this);
940
reed@android.com8a1c16f2008-12-17 15:59:43 +0000941 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
942
943 this->incReserve(5);
944
945 this->moveTo(left, top);
946 if (dir == kCCW_Direction) {
947 this->lineTo(left, bottom);
948 this->lineTo(right, bottom);
949 this->lineTo(right, top);
950 } else {
951 this->lineTo(right, top);
952 this->lineTo(right, bottom);
953 this->lineTo(left, bottom);
954 }
955 this->close();
956}
957
reed@google.com744faba2012-05-29 19:54:52 +0000958void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
959 SkDEBUGCODE(this->validate();)
960 if (count <= 0) {
961 return;
962 }
963
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000964 SkPathRef::Editor ed(&fPathRef);
965 fLastMoveToIndex = ed.pathRef()->countPoints();
966 uint8_t* vb;
967 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000968 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000969 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000970
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000971 memcpy(p, pts, count * sizeof(SkPoint));
972 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000973 if (count > 1) {
974 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
975 // be 0, the compiler will remove the test/branch entirely.
976 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000977 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000978 } else {
979 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000980 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000981 }
982 }
983 fSegmentMask |= kLine_SegmentMask;
984 }
985 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000986 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000987 }
988
989 GEN_ID_INC;
990 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000991 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000992}
993
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994static void add_corner_arc(SkPath* path, const SkRect& rect,
995 SkScalar rx, SkScalar ry, int startAngle,
996 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000997 // These two asserts are not sufficient, since really we want to know
998 // that the pair of radii (e.g. left and right, or top and bottom) sum
999 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001000 // these conservative asserts.
1001 SkASSERT(0 <= rx && rx <= rect.width());
1002 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +00001003
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkRect r;
1005 r.set(-rx, -ry, rx, ry);
1006
1007 switch (startAngle) {
1008 case 0:
1009 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1010 break;
1011 case 90:
1012 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1013 break;
1014 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1015 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001016 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 }
reed@google.comabf15c12011-01-18 20:35:51 +00001018
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 SkScalar start = SkIntToScalar(startAngle);
1020 SkScalar sweep = SkIntToScalar(90);
1021 if (SkPath::kCCW_Direction == dir) {
1022 start += sweep;
1023 sweep = -sweep;
1024 }
reed@google.comabf15c12011-01-18 20:35:51 +00001025
reed@android.com8a1c16f2008-12-17 15:59:43 +00001026 path->arcTo(r, start, sweep, forceMoveTo);
1027}
1028
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001029void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001031 SkRRect rrect;
1032 rrect.setRectRadii(rect, (const SkVector*) radii);
1033 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034}
1035
reed@google.com4ed0fb72012-12-12 20:48:18 +00001036void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001037 assert_known_direction(dir);
1038
1039 if (rrect.isEmpty()) {
1040 return;
1041 }
1042
reed@google.com4ed0fb72012-12-12 20:48:18 +00001043 const SkRect& bounds = rrect.getBounds();
1044
1045 if (rrect.isRect()) {
1046 this->addRect(bounds, dir);
1047 } else if (rrect.isOval()) {
1048 this->addOval(bounds, dir);
1049 } else if (rrect.isSimple()) {
1050 const SkVector& rad = rrect.getSimpleRadii();
1051 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1052 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001053 SkAutoPathBoundsUpdate apbu(this, bounds);
1054
1055 if (kCW_Direction == dir) {
1056 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1057 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1058 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1059 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1060 } else {
1061 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1062 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1063 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1064 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1065 }
1066 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001067 }
1068}
1069
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001070bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001071 int count = fPathRef->countVerbs();
1072 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1073 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074 if (*verbs == kLine_Verb ||
1075 *verbs == kQuad_Verb ||
1076 *verbs == kCubic_Verb) {
1077 return false;
1078 }
1079 ++verbs;
1080 }
1081 return true;
1082}
1083
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001084#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1085
1086void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1087 Direction dir) {
1088 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001089
humper@google.com75e3ca12013-04-08 21:44:11 +00001090 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001091 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001092 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001093 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001094 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1095 return;
1096 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001097
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001098 SkScalar w = rect.width();
1099 SkScalar halfW = SkScalarHalf(w);
1100 SkScalar h = rect.height();
1101 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001102
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001103 if (halfW <= 0 || halfH <= 0) {
1104 return;
1105 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001106
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001107 bool skip_hori = rx >= halfW;
1108 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001109
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001110 if (skip_hori && skip_vert) {
1111 this->addOval(rect, dir);
1112 return;
1113 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001114
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001115 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001116
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001117 SkAutoPathBoundsUpdate apbu(this, rect);
1118 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001119
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001120 if (skip_hori) {
1121 rx = halfW;
1122 } else if (skip_vert) {
1123 ry = halfH;
1124 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001125
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001126 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1127 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001128
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001129 this->incReserve(17);
1130 this->moveTo(rect.fRight - rx, rect.fTop);
1131 if (dir == kCCW_Direction) {
1132 if (!skip_hori) {
1133 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1134 }
1135 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1136 rect.fLeft, rect.fTop + ry - sy,
1137 rect.fLeft, rect.fTop + ry); // top-left
1138 if (!skip_vert) {
1139 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1140 }
1141 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1142 rect.fLeft + rx - sx, rect.fBottom,
1143 rect.fLeft + rx, rect.fBottom); // bot-left
1144 if (!skip_hori) {
1145 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1146 }
1147 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1148 rect.fRight, rect.fBottom - ry + sy,
1149 rect.fRight, rect.fBottom - ry); // bot-right
1150 if (!skip_vert) {
1151 this->lineTo(rect.fRight, rect.fTop + ry);
1152 }
1153 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1154 rect.fRight - rx + sx, rect.fTop,
1155 rect.fRight - rx, rect.fTop); // top-right
1156 } else {
1157 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1158 rect.fRight, rect.fTop + ry - sy,
1159 rect.fRight, rect.fTop + ry); // top-right
1160 if (!skip_vert) {
1161 this->lineTo(rect.fRight, rect.fBottom - ry);
1162 }
1163 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1164 rect.fRight - rx + sx, rect.fBottom,
1165 rect.fRight - rx, rect.fBottom); // bot-right
1166 if (!skip_hori) {
1167 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1168 }
1169 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1170 rect.fLeft, rect.fBottom - ry + sy,
1171 rect.fLeft, rect.fBottom - ry); // bot-left
1172 if (!skip_vert) {
1173 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1174 }
1175 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1176 rect.fLeft + rx - sx, rect.fTop,
1177 rect.fLeft + rx, rect.fTop); // top-left
1178 if (!skip_hori) {
1179 this->lineTo(rect.fRight - rx, rect.fTop); // top
1180 }
1181 }
1182 this->close();
1183}
1184
reed@android.com8a1c16f2008-12-17 15:59:43 +00001185void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001186 assert_known_direction(dir);
1187
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001188 /* If addOval() is called after previous moveTo(),
1189 this path is still marked as an oval. This is used to
1190 fit into WebKit's calling sequences.
1191 We can't simply check isEmpty() in this case, as additional
1192 moveTo() would mark the path non empty.
1193 */
1194 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001195 if (fIsOval) {
1196 fDirection = dir;
1197 } else {
1198 fDirection = kUnknown_Direction;
1199 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001200
1201 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001202 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001203
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 SkAutoPathBoundsUpdate apbu(this, oval);
1205
1206 SkScalar cx = oval.centerX();
1207 SkScalar cy = oval.centerY();
1208 SkScalar rx = SkScalarHalf(oval.width());
1209 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1212 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1213 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1214 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1215
1216 /*
1217 To handle imprecision in computing the center and radii, we revert to
1218 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1219 to ensure that we don't exceed the oval's bounds *ever*, since we want
1220 to use oval for our fast-bounds, rather than have to recompute it.
1221 */
1222 const SkScalar L = oval.fLeft; // cx - rx
1223 const SkScalar T = oval.fTop; // cy - ry
1224 const SkScalar R = oval.fRight; // cx + rx
1225 const SkScalar B = oval.fBottom; // cy + ry
1226
1227 this->incReserve(17); // 8 quads + close
1228 this->moveTo(R, cy);
1229 if (dir == kCCW_Direction) {
1230 this->quadTo( R, cy - sy, cx + mx, cy - my);
1231 this->quadTo(cx + sx, T, cx , T);
1232 this->quadTo(cx - sx, T, cx - mx, cy - my);
1233 this->quadTo( L, cy - sy, L, cy );
1234 this->quadTo( L, cy + sy, cx - mx, cy + my);
1235 this->quadTo(cx - sx, B, cx , B);
1236 this->quadTo(cx + sx, B, cx + mx, cy + my);
1237 this->quadTo( R, cy + sy, R, cy );
1238 } else {
1239 this->quadTo( R, cy + sy, cx + mx, cy + my);
1240 this->quadTo(cx + sx, B, cx , B);
1241 this->quadTo(cx - sx, B, cx - mx, cy + my);
1242 this->quadTo( L, cy + sy, L, cy );
1243 this->quadTo( L, cy - sy, cx - mx, cy - my);
1244 this->quadTo(cx - sx, T, cx , T);
1245 this->quadTo(cx + sx, T, cx + mx, cy - my);
1246 this->quadTo( R, cy - sy, R, cy );
1247 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 this->close();
1249}
1250
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001251bool SkPath::isOval(SkRect* rect) const {
1252 if (fIsOval && rect) {
1253 *rect = getBounds();
1254 }
1255
1256 return fIsOval;
1257}
1258
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1260 if (r > 0) {
1261 SkRect rect;
1262 rect.set(x - r, y - r, x + r, y + r);
1263 this->addOval(rect, dir);
1264 }
1265}
1266
1267#include "SkGeometry.h"
1268
1269static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1270 SkScalar sweepAngle,
1271 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001272
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001273 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001274 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1275 // Chrome uses this path to move into and out of ovals. If not
1276 // treated as a special case the moves can distort the oval's
1277 // bounding box (and break the circle special case).
1278 pts[0].set(oval.fRight, oval.centerY());
1279 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001280 } else if (0 == oval.width() && 0 == oval.height()) {
1281 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001282 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001283 // a rect.
1284 // TODO: optimizing the case where only one of width or height is zero
1285 // should also be considered. This case, however, doesn't seem to be
1286 // as common as the single point case.
1287 pts[0].set(oval.fRight, oval.fTop);
1288 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001289 }
1290
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 SkVector start, stop;
1292
1293 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1294 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1295 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001296
1297 /* If the sweep angle is nearly (but less than) 360, then due to precision
1298 loss in radians-conversion and/or sin/cos, we may end up with coincident
1299 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1300 of drawing a nearly complete circle (good).
1301 e.g. canvas.drawArc(0, 359.99, ...)
1302 -vs- canvas.drawArc(0, 359.9, ...)
1303 We try to detect this edge case, and tweak the stop vector
1304 */
1305 if (start == stop) {
1306 SkScalar sw = SkScalarAbs(sweepAngle);
1307 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1308 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1309 // make a guess at a tiny angle (in radians) to tweak by
1310 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1311 // not sure how much will be enough, so we use a loop
1312 do {
1313 stopRad -= deltaRad;
1314 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1315 } while (start == stop);
1316 }
1317 }
1318
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001320
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1322 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001323
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 return SkBuildQuadArc(start, stop,
1325 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1326 &matrix, pts);
1327}
1328
1329void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1330 bool forceMoveTo) {
1331 if (oval.width() < 0 || oval.height() < 0) {
1332 return;
1333 }
1334
1335 SkPoint pts[kSkBuildQuadArcStorage];
1336 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1337 SkASSERT((count & 1) == 1);
1338
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001339 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 forceMoveTo = true;
1341 }
1342 this->incReserve(count);
1343 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1344 for (int i = 1; i < count; i += 2) {
1345 this->quadTo(pts[i], pts[i+1]);
1346 }
1347}
1348
1349void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1350 SkScalar sweepAngle) {
1351 if (oval.isEmpty() || 0 == sweepAngle) {
1352 return;
1353 }
1354
1355 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1356
1357 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1358 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1359 return;
1360 }
1361
1362 SkPoint pts[kSkBuildQuadArcStorage];
1363 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1364
1365 this->incReserve(count);
1366 this->moveTo(pts[0]);
1367 for (int i = 1; i < count; i += 2) {
1368 this->quadTo(pts[i], pts[i+1]);
1369 }
1370}
1371
1372/*
1373 Need to handle the case when the angle is sharp, and our computed end-points
1374 for the arc go behind pt1 and/or p2...
1375*/
1376void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1377 SkScalar radius) {
1378 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001379
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 // need to know our prev pt so we can construct tangent vectors
1381 {
1382 SkPoint start;
1383 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001384 // Handle degenerate cases by adding a line to the first point and
1385 // bailing out.
1386 if ((x1 == start.fX && y1 == start.fY) ||
1387 (x1 == x2 && y1 == y2) ||
1388 radius == 0) {
1389 this->lineTo(x1, y1);
1390 return;
1391 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001392 before.setNormalize(x1 - start.fX, y1 - start.fY);
1393 after.setNormalize(x2 - x1, y2 - y1);
1394 }
reed@google.comabf15c12011-01-18 20:35:51 +00001395
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 SkScalar cosh = SkPoint::DotProduct(before, after);
1397 SkScalar sinh = SkPoint::CrossProduct(before, after);
1398
1399 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001400 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 return;
1402 }
reed@google.comabf15c12011-01-18 20:35:51 +00001403
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1405 if (dist < 0) {
1406 dist = -dist;
1407 }
1408
1409 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1410 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1411 SkRotationDirection arcDir;
1412
1413 // now turn before/after into normals
1414 if (sinh > 0) {
1415 before.rotateCCW();
1416 after.rotateCCW();
1417 arcDir = kCW_SkRotationDirection;
1418 } else {
1419 before.rotateCW();
1420 after.rotateCW();
1421 arcDir = kCCW_SkRotationDirection;
1422 }
1423
1424 SkMatrix matrix;
1425 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001426
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 matrix.setScale(radius, radius);
1428 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1429 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001430
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001432
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 this->incReserve(count);
1434 // [xx,yy] == pts[0]
1435 this->lineTo(xx, yy);
1436 for (int i = 1; i < count; i += 2) {
1437 this->quadTo(pts[i], pts[i+1]);
1438 }
1439}
1440
1441///////////////////////////////////////////////////////////////////////////////
1442
1443void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1444 SkMatrix matrix;
1445
1446 matrix.setTranslate(dx, dy);
1447 this->addPath(path, matrix);
1448}
1449
1450void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001451 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001453 fIsOval = false;
1454
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001455 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 SkPoint pts[4];
1457 Verb verb;
1458
1459 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1460
1461 while ((verb = iter.next(pts)) != kDone_Verb) {
1462 switch (verb) {
1463 case kMove_Verb:
1464 proc(matrix, &pts[0], &pts[0], 1);
1465 this->moveTo(pts[0]);
1466 break;
1467 case kLine_Verb:
1468 proc(matrix, &pts[1], &pts[1], 1);
1469 this->lineTo(pts[1]);
1470 break;
1471 case kQuad_Verb:
1472 proc(matrix, &pts[1], &pts[1], 2);
1473 this->quadTo(pts[1], pts[2]);
1474 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001475 case kConic_Verb:
1476 proc(matrix, &pts[1], &pts[1], 2);
1477 this->conicTo(pts[1], pts[2], iter.conicWeight());
1478 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 case kCubic_Verb:
1480 proc(matrix, &pts[1], &pts[1], 3);
1481 this->cubicTo(pts[1], pts[2], pts[3]);
1482 break;
1483 case kClose_Verb:
1484 this->close();
1485 break;
1486 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001487 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 }
1489 }
1490}
1491
1492///////////////////////////////////////////////////////////////////////////////
1493
reed@google.com277c3f82013-05-31 15:17:50 +00001494static int pts_in_verb(unsigned verb) {
1495 static const uint8_t gPtsInVerb[] = {
1496 1, // kMove
1497 1, // kLine
1498 2, // kQuad
1499 2, // kConic
1500 3, // kCubic
1501 0, // kClose
1502 0 // kDone
1503 };
1504
1505 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1506 return gPtsInVerb[verb];
1507}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508
1509// ignore the initial moveto, and stop when the 1st contour ends
1510void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001511 int i, vcount = path.fPathRef->countVerbs();
1512 // exit early if the path is empty, or just has a moveTo.
1513 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 return;
1515 }
1516
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001519 fIsOval = false;
1520
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001521 const uint8_t* verbs = path.fPathRef->verbs();
1522 // skip the initial moveTo
1523 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001524 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 case kLine_Verb:
1530 this->lineTo(pts[0].fX, pts[0].fY);
1531 break;
1532 case kQuad_Verb:
1533 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1534 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001535 case kConic_Verb:
1536 this->conicTo(pts[0], pts[1], *conicWeight++);
1537 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 break;
1541 case kClose_Verb:
1542 return;
1543 }
reed@google.com277c3f82013-05-31 15:17:50 +00001544 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 }
1546}
1547
1548// ignore the last point of the 1st contour
1549void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 int i, vcount = path.fPathRef->countVerbs();
1551 // exit early if the path is empty, or just has a moveTo.
1552 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 return;
1554 }
1555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001558 fIsOval = false;
1559
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001560 const uint8_t* verbs = path.fPathRef->verbs();
1561 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001562 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001564 SkASSERT(verbs[~0] == kMove_Verb);
1565 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001566 unsigned v = verbs[~i];
1567 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 if (n == 0) {
1569 break;
1570 }
1571 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001572 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 }
1574
1575 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001576 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 case kLine_Verb:
1578 this->lineTo(pts[-1].fX, pts[-1].fY);
1579 break;
1580 case kQuad_Verb:
1581 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1582 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001583 case kConic_Verb:
1584 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1585 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 case kCubic_Verb:
1587 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1588 pts[-3].fX, pts[-3].fY);
1589 break;
1590 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001591 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 break;
1593 }
reed@google.com277c3f82013-05-31 15:17:50 +00001594 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 }
1596}
1597
reed@google.com63d73742012-01-10 15:33:12 +00001598void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001599 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001600
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001601 const SkPoint* pts = src.fPathRef->pointsEnd();
1602 // we will iterator through src's verbs backwards
1603 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1604 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001605 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001606
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001607 fIsOval = false;
1608
reed@google.com63d73742012-01-10 15:33:12 +00001609 bool needMove = true;
1610 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001611 while (verbs < verbsEnd) {
1612 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001613 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001614
1615 if (needMove) {
1616 --pts;
1617 this->moveTo(pts->fX, pts->fY);
1618 needMove = false;
1619 }
1620 pts -= n;
1621 switch (v) {
1622 case kMove_Verb:
1623 if (needClose) {
1624 this->close();
1625 needClose = false;
1626 }
1627 needMove = true;
1628 pts += 1; // so we see the point in "if (needMove)" above
1629 break;
1630 case kLine_Verb:
1631 this->lineTo(pts[0]);
1632 break;
1633 case kQuad_Verb:
1634 this->quadTo(pts[1], pts[0]);
1635 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001636 case kConic_Verb:
1637 this->conicTo(pts[1], pts[0], *--conicWeights);
1638 break;
reed@google.com63d73742012-01-10 15:33:12 +00001639 case kCubic_Verb:
1640 this->cubicTo(pts[2], pts[1], pts[0]);
1641 break;
1642 case kClose_Verb:
1643 needClose = true;
1644 break;
1645 default:
1646 SkASSERT(!"unexpected verb");
1647 }
1648 }
1649}
1650
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651///////////////////////////////////////////////////////////////////////////////
1652
1653void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1654 SkMatrix matrix;
1655
1656 matrix.setTranslate(dx, dy);
1657 this->transform(matrix, dst);
1658}
1659
1660#include "SkGeometry.h"
1661
1662static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1663 int level = 2) {
1664 if (--level >= 0) {
1665 SkPoint tmp[5];
1666
1667 SkChopQuadAtHalf(pts, tmp);
1668 subdivide_quad_to(path, &tmp[0], level);
1669 subdivide_quad_to(path, &tmp[2], level);
1670 } else {
1671 path->quadTo(pts[1], pts[2]);
1672 }
1673}
1674
1675static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1676 int level = 2) {
1677 if (--level >= 0) {
1678 SkPoint tmp[7];
1679
1680 SkChopCubicAtHalf(pts, tmp);
1681 subdivide_cubic_to(path, &tmp[0], level);
1682 subdivide_cubic_to(path, &tmp[3], level);
1683 } else {
1684 path->cubicTo(pts[1], pts[2], pts[3]);
1685 }
1686}
1687
1688void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1689 SkDEBUGCODE(this->validate();)
1690 if (dst == NULL) {
1691 dst = (SkPath*)this;
1692 }
1693
tomhudson@google.com8d430182011-06-06 19:11:19 +00001694 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 SkPath tmp;
1696 tmp.fFillType = fFillType;
1697
1698 SkPath::Iter iter(*this, false);
1699 SkPoint pts[4];
1700 SkPath::Verb verb;
1701
reed@google.com4a3b7142012-05-16 17:16:46 +00001702 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 switch (verb) {
1704 case kMove_Verb:
1705 tmp.moveTo(pts[0]);
1706 break;
1707 case kLine_Verb:
1708 tmp.lineTo(pts[1]);
1709 break;
1710 case kQuad_Verb:
1711 subdivide_quad_to(&tmp, pts);
1712 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001713 case kConic_Verb:
1714 SkASSERT(!"TODO: compute new weight");
1715 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1716 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 case kCubic_Verb:
1718 subdivide_cubic_to(&tmp, pts);
1719 break;
1720 case kClose_Verb:
1721 tmp.close();
1722 break;
1723 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001724 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 break;
1726 }
1727 }
1728
1729 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001730 SkPathRef::Editor ed(&dst->fPathRef);
1731 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001732 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001734 /*
1735 * If we're not in perspective, we can transform all of the points at
1736 * once.
1737 *
1738 * Here we also want to optimize bounds, by noting if the bounds are
1739 * already known, and if so, we just transform those as well and mark
1740 * them as "known", rather than force the transformed path to have to
1741 * recompute them.
1742 *
1743 * Special gotchas if the path is effectively empty (<= 1 point) or
1744 * if it is non-finite. In those cases bounds need to stay empty,
1745 * regardless of the matrix.
1746 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001748 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001749 if (fIsFinite) {
1750 matrix.mapRect(&dst->fBounds, fBounds);
1751 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1752 dst->fBounds.setEmpty();
1753 }
1754 } else {
1755 dst->fIsFinite = false;
1756 dst->fBounds.setEmpty();
1757 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001759 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001760 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 }
1762
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1764
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001767 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001768 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001770
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001771#ifdef SK_BUILD_FOR_ANDROID
1772 if (!matrix.isIdentity()) {
1773 GEN_ID_PTR_INC(dst);
1774 }
1775#endif
1776
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001777 if (kUnknown_Direction == fDirection) {
1778 dst->fDirection = kUnknown_Direction;
1779 } else {
1780 SkScalar det2x2 =
1781 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1782 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1783 if (det2x2 < 0) {
1784 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1785 } else if (det2x2 > 0) {
1786 dst->fDirection = fDirection;
1787 } else {
1788 dst->fDirection = kUnknown_Direction;
1789 }
1790 }
1791
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 // It's an oval only if it stays a rect.
1793 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001794
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 SkDEBUGCODE(dst->validate();)
1796 }
1797}
1798
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799///////////////////////////////////////////////////////////////////////////////
1800///////////////////////////////////////////////////////////////////////////////
1801
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001803 kEmptyContour_SegmentState, // The current contour is empty. We may be
1804 // starting processing or we may have just
1805 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1807 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1808 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809};
1810
1811SkPath::Iter::Iter() {
1812#ifdef SK_DEBUG
1813 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001814 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001817 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818#endif
1819 // need to init enough to make next() harmlessly return kDone_Verb
1820 fVerbs = NULL;
1821 fVerbStop = NULL;
1822 fNeedClose = false;
1823}
1824
1825SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1826 this->setPath(path, forceClose);
1827}
1828
1829void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 fPts = path.fPathRef->points();
1831 fVerbs = path.fPathRef->verbs();
1832 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001833 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001835 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836 fForceClose = SkToU8(forceClose);
1837 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001838 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839}
1840
1841bool SkPath::Iter::isClosedContour() const {
1842 if (fVerbs == NULL || fVerbs == fVerbStop) {
1843 return false;
1844 }
1845 if (fForceClose) {
1846 return true;
1847 }
1848
1849 const uint8_t* verbs = fVerbs;
1850 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001851
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001852 if (kMove_Verb == *(verbs - 1)) {
1853 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 }
1855
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001856 while (verbs > stop) {
1857 // verbs points one beyond the current verb, decrement first.
1858 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 if (kMove_Verb == v) {
1860 break;
1861 }
1862 if (kClose_Verb == v) {
1863 return true;
1864 }
1865 }
1866 return false;
1867}
1868
1869SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001870 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001872 // A special case: if both points are NaN, SkPoint::operation== returns
1873 // false, but the iterator expects that they are treated as the same.
1874 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001875 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1876 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001877 return kClose_Verb;
1878 }
1879
reed@google.com9e25dbf2012-05-15 17:05:38 +00001880 pts[0] = fLastPt;
1881 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 fLastPt = fMoveTo;
1883 fCloseLine = true;
1884 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001885 } else {
1886 pts[0] = fMoveTo;
1887 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889}
1890
reed@google.com9e25dbf2012-05-15 17:05:38 +00001891const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 if (fSegmentState == kAfterMove_SegmentState) {
1893 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001895 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1898 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901}
1902
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903void SkPath::Iter::consumeDegenerateSegments() {
1904 // We need to step over anything that will not move the current draw point
1905 // forward before the next move is seen
1906 const uint8_t* lastMoveVerb = 0;
1907 const SkPoint* lastMovePt = 0;
1908 SkPoint lastPt = fLastPt;
1909 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001910 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 switch (verb) {
1912 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 // Keep a record of this most recent move
1914 lastMoveVerb = fVerbs;
1915 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001916 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001917 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fPts++;
1919 break;
1920
1921 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001922 // A close when we are in a segment is always valid except when it
1923 // follows a move which follows a segment.
1924 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 return;
1926 }
1927 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001928 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 break;
1930
1931 case kLine_Verb:
1932 if (!IsLineDegenerate(lastPt, fPts[0])) {
1933 if (lastMoveVerb) {
1934 fVerbs = lastMoveVerb;
1935 fPts = lastMovePt;
1936 return;
1937 }
1938 return;
1939 }
1940 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001941 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 fPts++;
1943 break;
1944
reed@google.com277c3f82013-05-31 15:17:50 +00001945 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 case kQuad_Verb:
1947 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1948 if (lastMoveVerb) {
1949 fVerbs = lastMoveVerb;
1950 fPts = lastMovePt;
1951 return;
1952 }
1953 return;
1954 }
1955 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001958 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 break;
1960
1961 case kCubic_Verb:
1962 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1963 if (lastMoveVerb) {
1964 fVerbs = lastMoveVerb;
1965 fPts = lastMovePt;
1966 return;
1967 }
1968 return;
1969 }
1970 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 fPts += 3;
1973 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001974
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001975 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001976 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 }
1978 }
1979}
1980
reed@google.com4a3b7142012-05-16 17:16:46 +00001981SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001982 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 // Close the curve if requested and if there is some curve to close
1986 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001987 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001988 return kLine_Verb;
1989 }
1990 fNeedClose = false;
1991 return kClose_Verb;
1992 }
1993 return kDone_Verb;
1994 }
1995
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001996 // fVerbs is one beyond the current verb, decrement first
1997 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001998 const SkPoint* SK_RESTRICT srcPts = fPts;
1999 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000
2001 switch (verb) {
2002 case kMove_Verb:
2003 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 verb = this->autoClose(pts);
2006 if (verb == kClose_Verb) {
2007 fNeedClose = false;
2008 }
2009 return (Verb)verb;
2010 }
2011 if (fVerbs == fVerbStop) { // might be a trailing moveto
2012 return kDone_Verb;
2013 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002014 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002015 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002017 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002018 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 fNeedClose = fForceClose;
2020 break;
2021 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002022 pts[0] = this->cons_moveTo();
2023 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 fLastPt = srcPts[0];
2025 fCloseLine = false;
2026 srcPts += 1;
2027 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002028 case kConic_Verb:
2029 fConicWeights += 1;
2030 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002032 pts[0] = this->cons_moveTo();
2033 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 fLastPt = srcPts[1];
2035 srcPts += 2;
2036 break;
2037 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002038 pts[0] = this->cons_moveTo();
2039 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 fLastPt = srcPts[2];
2041 srcPts += 3;
2042 break;
2043 case kClose_Verb:
2044 verb = this->autoClose(pts);
2045 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002046 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 } else {
2048 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002049 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002051 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 break;
2053 }
2054 fPts = srcPts;
2055 return (Verb)verb;
2056}
2057
2058///////////////////////////////////////////////////////////////////////////////
2059
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002060SkPath::RawIter::RawIter() {
2061#ifdef SK_DEBUG
2062 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002063 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002064 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2065#endif
2066 // need to init enough to make next() harmlessly return kDone_Verb
2067 fVerbs = NULL;
2068 fVerbStop = NULL;
2069}
2070
2071SkPath::RawIter::RawIter(const SkPath& path) {
2072 this->setPath(path);
2073}
2074
2075void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002076 fPts = path.fPathRef->points();
2077 fVerbs = path.fPathRef->verbs();
2078 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002079 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002080 fMoveTo.fX = fMoveTo.fY = 0;
2081 fLastPt.fX = fLastPt.fY = 0;
2082}
2083
2084SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002085 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002086 if (fVerbs == fVerbStop) {
2087 return kDone_Verb;
2088 }
2089
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002090 // fVerbs points one beyond next verb so decrement first.
2091 unsigned verb = *(--fVerbs);
2092 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002093
2094 switch (verb) {
2095 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002096 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002097 fMoveTo = srcPts[0];
2098 fLastPt = fMoveTo;
2099 srcPts += 1;
2100 break;
2101 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002102 pts[0] = fLastPt;
2103 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002104 fLastPt = srcPts[0];
2105 srcPts += 1;
2106 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002107 case kConic_Verb:
2108 fConicWeights += 1;
2109 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002110 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002111 pts[0] = fLastPt;
2112 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002113 fLastPt = srcPts[1];
2114 srcPts += 2;
2115 break;
2116 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002117 pts[0] = fLastPt;
2118 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002119 fLastPt = srcPts[2];
2120 srcPts += 3;
2121 break;
2122 case kClose_Verb:
2123 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002124 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002125 break;
2126 }
2127 fPts = srcPts;
2128 return (Verb)verb;
2129}
2130
2131///////////////////////////////////////////////////////////////////////////////
2132
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002134 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135*/
2136
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002137uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 SkDEBUGCODE(this->validate();)
2139
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002140 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002141 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002142 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002143 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002144 return SkAlign4(byteCount);
2145 }
2146
2147 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002148
2149 // Call getBounds() to ensure (as a side-effect) that fBounds
2150 // and fIsFinite are computed.
2151 const SkRect& bounds = this->getBounds();
2152 SkASSERT(!fBoundsIsDirty);
2153
2154 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2155 ((fIsOval & 1) << kIsOval_SerializationShift) |
2156 (fConvexity << kConvexity_SerializationShift) |
2157 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002158 (fSegmentMask << kSegmentMask_SerializationShift) |
2159 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002160
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002161 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002162
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002163 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002164
2165 buffer.write(&bounds, sizeof(bounds));
2166
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002167 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002168 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169}
2170
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002171uint32_t SkPath::readFromMemory(const void* storage) {
2172 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002173
reed@google.com98b11f12011-09-21 18:40:27 +00002174 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002175 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2176 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2177 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2178 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002179 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002180 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002181
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002182 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002183
2184 buffer.read(&fBounds, sizeof(fBounds));
2185 fBoundsIsDirty = false;
2186
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002187 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002188
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002189 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190
2191 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002192 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193}
2194
2195///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196
reed@google.com51bbe752013-01-17 22:07:50 +00002197#include "SkString.h"
2198
2199static void append_scalar(SkString* str, SkScalar value) {
2200 SkString tmp;
2201 tmp.printf("%g", value);
2202 if (tmp.contains('.')) {
2203 tmp.appendUnichar('f');
2204 }
2205 str->append(tmp);
2206}
2207
2208static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002209 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002210 str->append(label);
2211 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002212
reed@google.com51bbe752013-01-17 22:07:50 +00002213 const SkScalar* values = &pts[0].fX;
2214 count *= 2;
2215
2216 for (int i = 0; i < count; ++i) {
2217 append_scalar(str, values[i]);
2218 if (i < count - 1) {
2219 str->append(", ");
2220 }
2221 }
reed@google.com277c3f82013-05-31 15:17:50 +00002222 if (conicWeight >= 0) {
2223 str->append(", ");
2224 append_scalar(str, conicWeight);
2225 }
reed@google.com51bbe752013-01-17 22:07:50 +00002226 str->append(");\n");
2227}
2228
reed@android.com8a1c16f2008-12-17 15:59:43 +00002229void SkPath::dump(bool forceClose, const char title[]) const {
2230 Iter iter(*this, forceClose);
2231 SkPoint pts[4];
2232 Verb verb;
2233
2234 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2235 title ? title : "");
2236
reed@google.com51bbe752013-01-17 22:07:50 +00002237 SkString builder;
2238
reed@google.com4a3b7142012-05-16 17:16:46 +00002239 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002240 switch (verb) {
2241 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002242 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002243 break;
2244 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002245 append_params(&builder, "path.lineTo", &pts[1], 1);
2246 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002247 break;
2248 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002249 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002250 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002251 case kConic_Verb:
2252 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2253 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002254 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002255 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002256 break;
2257 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002258 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002259 break;
2260 default:
2261 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2262 verb = kDone_Verb; // stop the loop
2263 break;
2264 }
2265 }
reed@google.com51bbe752013-01-17 22:07:50 +00002266 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002267}
2268
reed@android.come522ca52009-11-23 20:10:41 +00002269void SkPath::dump() const {
2270 this->dump(false);
2271}
2272
2273#ifdef SK_DEBUG
2274void SkPath::validate() const {
2275 SkASSERT(this != NULL);
2276 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002277
djsollen@google.com077348c2012-10-22 20:23:32 +00002278#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002279 if (!fBoundsIsDirty) {
2280 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002281
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002282 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002283 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002284
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002285 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002286 // if we're empty, fBounds may be empty but translated, so we can't
2287 // necessarily compare to bounds directly
2288 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2289 // be [2, 2, 2, 2]
2290 SkASSERT(bounds.isEmpty());
2291 SkASSERT(fBounds.isEmpty());
2292 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002293 if (bounds.isEmpty()) {
2294 SkASSERT(fBounds.isEmpty());
2295 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002296 if (!fBounds.isEmpty()) {
2297 SkASSERT(fBounds.contains(bounds));
2298 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002299 }
reed@android.come522ca52009-11-23 20:10:41 +00002300 }
2301 }
reed@google.com10296cc2011-09-21 12:29:05 +00002302
2303 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002304 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2305 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2306 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002307 case kLine_Verb:
2308 mask |= kLine_SegmentMask;
2309 break;
2310 case kQuad_Verb:
2311 mask |= kQuad_SegmentMask;
2312 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002313 case kConic_Verb:
2314 mask |= kConic_SegmentMask;
2315 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002316 case kCubic_Verb:
2317 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002318 case kMove_Verb: // these verbs aren't included in the segment mask.
2319 case kClose_Verb:
2320 break;
2321 case kDone_Verb:
2322 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2323 break;
2324 default:
2325 SkDEBUGFAIL("Unknown Verb");
2326 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002327 }
2328 }
2329 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002330#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002331}
djsollen@google.com077348c2012-10-22 20:23:32 +00002332#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002333
reed@google.com04863fa2011-05-15 04:08:24 +00002334///////////////////////////////////////////////////////////////////////////////
2335
reed@google.com0b7b9822011-05-16 12:29:27 +00002336static int sign(SkScalar x) { return x < 0; }
2337#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002338
reed@google.com04863fa2011-05-15 04:08:24 +00002339static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002340 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002341}
2342
2343// only valid for a single contour
2344struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002345 Convexicator()
2346 : fPtCount(0)
2347 , fConvexity(SkPath::kConvex_Convexity)
2348 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002349 fSign = 0;
2350 // warnings
2351 fCurrPt.set(0, 0);
2352 fVec0.set(0, 0);
2353 fVec1.set(0, 0);
2354 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002355
2356 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002357 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002358 }
2359
2360 SkPath::Convexity getConvexity() const { return fConvexity; }
2361
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002362 /** The direction returned is only valid if the path is determined convex */
2363 SkPath::Direction getDirection() const { return fDirection; }
2364
reed@google.com04863fa2011-05-15 04:08:24 +00002365 void addPt(const SkPoint& pt) {
2366 if (SkPath::kConcave_Convexity == fConvexity) {
2367 return;
2368 }
2369
2370 if (0 == fPtCount) {
2371 fCurrPt = pt;
2372 ++fPtCount;
2373 } else {
2374 SkVector vec = pt - fCurrPt;
2375 if (vec.fX || vec.fY) {
2376 fCurrPt = pt;
2377 if (++fPtCount == 2) {
2378 fFirstVec = fVec1 = vec;
2379 } else {
2380 SkASSERT(fPtCount > 2);
2381 this->addVec(vec);
2382 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002383
reed@google.com85b6e392011-05-15 20:25:17 +00002384 int sx = sign(vec.fX);
2385 int sy = sign(vec.fY);
2386 fDx += (sx != fSx);
2387 fDy += (sy != fSy);
2388 fSx = sx;
2389 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002390
reed@google.com85b6e392011-05-15 20:25:17 +00002391 if (fDx > 3 || fDy > 3) {
2392 fConvexity = SkPath::kConcave_Convexity;
2393 }
reed@google.com04863fa2011-05-15 04:08:24 +00002394 }
2395 }
2396 }
2397
2398 void close() {
2399 if (fPtCount > 2) {
2400 this->addVec(fFirstVec);
2401 }
2402 }
2403
2404private:
2405 void addVec(const SkVector& vec) {
2406 SkASSERT(vec.fX || vec.fY);
2407 fVec0 = fVec1;
2408 fVec1 = vec;
2409 int sign = CrossProductSign(fVec0, fVec1);
2410 if (0 == fSign) {
2411 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002412 if (1 == sign) {
2413 fDirection = SkPath::kCW_Direction;
2414 } else if (-1 == sign) {
2415 fDirection = SkPath::kCCW_Direction;
2416 }
reed@google.com04863fa2011-05-15 04:08:24 +00002417 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002418 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002419 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 }
2422 }
2423 }
2424
2425 SkPoint fCurrPt;
2426 SkVector fVec0, fVec1, fFirstVec;
2427 int fPtCount; // non-degenerate points
2428 int fSign;
2429 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002430 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002431 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002432};
2433
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002434SkPath::Convexity SkPath::internalGetConvexity() const {
2435 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002436 SkPoint pts[4];
2437 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002438 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002439
2440 int contourCount = 0;
2441 int count;
2442 Convexicator state;
2443
2444 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2445 switch (verb) {
2446 case kMove_Verb:
2447 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 return kConcave_Convexity;
2450 }
2451 pts[1] = pts[0];
2452 count = 1;
2453 break;
2454 case kLine_Verb: count = 1; break;
2455 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002456 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002457 case kCubic_Verb: count = 3; break;
2458 case kClose_Verb:
2459 state.close();
2460 count = 0;
2461 break;
2462 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002463 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002464 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002465 return kConcave_Convexity;
2466 }
2467
2468 for (int i = 1; i <= count; i++) {
2469 state.addPt(pts[i]);
2470 }
2471 // early exit
2472 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002473 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002474 return kConcave_Convexity;
2475 }
2476 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002477 fConvexity = state.getConvexity();
2478 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2479 fDirection = state.getDirection();
2480 }
2481 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002482}
reed@google.com69a99432012-01-10 18:00:10 +00002483
2484///////////////////////////////////////////////////////////////////////////////
2485
2486class ContourIter {
2487public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002488 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002489
2490 bool done() const { return fDone; }
2491 // if !done() then these may be called
2492 int count() const { return fCurrPtCount; }
2493 const SkPoint* pts() const { return fCurrPt; }
2494 void next();
2495
2496private:
2497 int fCurrPtCount;
2498 const SkPoint* fCurrPt;
2499 const uint8_t* fCurrVerb;
2500 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002501 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002502 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002503 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002504};
2505
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002506ContourIter::ContourIter(const SkPathRef& pathRef) {
2507 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002508 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002509 fCurrPt = pathRef.points();
2510 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002511 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002512 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002513 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002514 this->next();
2515}
2516
2517void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002518 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002519 fDone = true;
2520 }
2521 if (fDone) {
2522 return;
2523 }
2524
2525 // skip pts of prev contour
2526 fCurrPt += fCurrPtCount;
2527
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002528 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002529 int ptCount = 1; // moveTo
2530 const uint8_t* verbs = fCurrVerb;
2531
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002532 for (--verbs; verbs > fStopVerbs; --verbs) {
2533 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002534 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002535 goto CONTOUR_END;
2536 case SkPath::kLine_Verb:
2537 ptCount += 1;
2538 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002539 case SkPath::kConic_Verb:
2540 fCurrConicWeight += 1;
2541 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002542 case SkPath::kQuad_Verb:
2543 ptCount += 2;
2544 break;
2545 case SkPath::kCubic_Verb:
2546 ptCount += 3;
2547 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002548 case SkPath::kClose_Verb:
2549 break;
2550 default:
2551 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002552 break;
2553 }
2554 }
2555CONTOUR_END:
2556 fCurrPtCount = ptCount;
2557 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002558 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002559}
2560
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002561// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002562static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002563 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2564 // We may get 0 when the above subtracts underflow. We expect this to be
2565 // very rare and lazily promote to double.
2566 if (0 == cross) {
2567 double p0x = SkScalarToDouble(p0.fX);
2568 double p0y = SkScalarToDouble(p0.fY);
2569
2570 double p1x = SkScalarToDouble(p1.fX);
2571 double p1y = SkScalarToDouble(p1.fY);
2572
2573 double p2x = SkScalarToDouble(p2.fX);
2574 double p2y = SkScalarToDouble(p2.fY);
2575
2576 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2577 (p1y - p0y) * (p2x - p0x));
2578
2579 }
2580 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002581}
2582
reed@google.comc1ea60a2012-01-31 15:15:36 +00002583// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002584static int find_max_y(const SkPoint pts[], int count) {
2585 SkASSERT(count > 0);
2586 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002587 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002588 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002589 SkScalar y = pts[i].fY;
2590 if (y > max) {
2591 max = y;
2592 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002593 }
2594 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002595 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002596}
2597
reed@google.comcabaf1d2012-01-11 21:03:05 +00002598static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2599 int i = index;
2600 for (;;) {
2601 i = (i + inc) % n;
2602 if (i == index) { // we wrapped around, so abort
2603 break;
2604 }
2605 if (pts[index] != pts[i]) { // found a different point, success!
2606 break;
2607 }
2608 }
2609 return i;
2610}
2611
reed@google.comc1ea60a2012-01-31 15:15:36 +00002612/**
2613 * Starting at index, and moving forward (incrementing), find the xmin and
2614 * xmax of the contiguous points that have the same Y.
2615 */
2616static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2617 int* maxIndexPtr) {
2618 const SkScalar y = pts[index].fY;
2619 SkScalar min = pts[index].fX;
2620 SkScalar max = min;
2621 int minIndex = index;
2622 int maxIndex = index;
2623 for (int i = index + 1; i < n; ++i) {
2624 if (pts[i].fY != y) {
2625 break;
2626 }
2627 SkScalar x = pts[i].fX;
2628 if (x < min) {
2629 min = x;
2630 minIndex = i;
2631 } else if (x > max) {
2632 max = x;
2633 maxIndex = i;
2634 }
2635 }
2636 *maxIndexPtr = maxIndex;
2637 return minIndex;
2638}
2639
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002640static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002641 if (dir) {
2642 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2643 }
reed@google.comac8543f2012-01-30 20:51:25 +00002644}
2645
reed@google.comc1ea60a2012-01-31 15:15:36 +00002646#if 0
2647#include "SkString.h"
2648#include "../utils/SkParsePath.h"
2649static void dumpPath(const SkPath& path) {
2650 SkString str;
2651 SkParsePath::ToSVGString(path, &str);
2652 SkDebugf("%s\n", str.c_str());
2653}
2654#endif
2655
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002656namespace {
2657// for use with convex_dir_test
2658double mul(double a, double b) { return a * b; }
2659SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2660double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2661SkScalar toScalar(SkScalar a) { return a; }
2662
2663// determines the winding direction of a convex polygon with the precision
2664// of T. CAST_SCALAR casts an SkScalar to T.
2665template <typename T, T (CAST_SCALAR)(SkScalar)>
2666bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2667 // we find the first three points that form a non-degenerate
2668 // triangle. If there are no such points then the path is
2669 // degenerate. The first is always point 0. Now we find the second
2670 // point.
2671 int i = 0;
2672 enum { kX = 0, kY = 1 };
2673 T v0[2];
2674 while (1) {
2675 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2676 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2677 if (v0[kX] || v0[kY]) {
2678 break;
2679 }
2680 if (++i == n - 1) {
2681 return false;
2682 }
2683 }
2684 // now find a third point that is not colinear with the first two
2685 // points and check the orientation of the triangle (which will be
2686 // the same as the orientation of the path).
2687 for (++i; i < n; ++i) {
2688 T v1[2];
2689 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2690 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2691 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2692 if (0 != cross) {
2693 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2694 return true;
2695 }
2696 }
2697 return false;
2698}
2699}
2700
reed@google.comac8543f2012-01-30 20:51:25 +00002701/*
2702 * We loop through all contours, and keep the computed cross-product of the
2703 * contour that contained the global y-max. If we just look at the first
2704 * contour, we may find one that is wound the opposite way (correctly) since
2705 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2706 * that is outer most (or at least has the global y-max) before we can consider
2707 * its cross product.
2708 */
reed@google.com69a99432012-01-10 18:00:10 +00002709bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002710// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002711 // don't want to pay the cost for computing this if it
2712 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002713
2714 if (kUnknown_Direction != fDirection) {
2715 *dir = static_cast<Direction>(fDirection);
2716 return true;
2717 }
reed@google.com69a99432012-01-10 18:00:10 +00002718 const Convexity conv = this->getConvexityOrUnknown();
2719
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002720 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002721
reed@google.comac8543f2012-01-30 20:51:25 +00002722 // initialize with our logical y-min
2723 SkScalar ymax = this->getBounds().fTop;
2724 SkScalar ymaxCross = 0;
2725
reed@google.com69a99432012-01-10 18:00:10 +00002726 for (; !iter.done(); iter.next()) {
2727 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002728 if (n < 3) {
2729 continue;
2730 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002731
reed@google.comcabaf1d2012-01-11 21:03:05 +00002732 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002733 SkScalar cross = 0;
2734 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002735 // We try first at scalar precision, and then again at double
2736 // precision. This is because the vectors computed between distant
2737 // points may lose too much precision.
2738 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002739 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002740 return true;
2741 }
2742 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002743 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002744 return true;
2745 } else {
2746 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002747 }
2748 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002749 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002750 if (pts[index].fY < ymax) {
2751 continue;
2752 }
2753
reed@google.comc1ea60a2012-01-31 15:15:36 +00002754 // If there is more than 1 distinct point at the y-max, we take the
2755 // x-min and x-max of them and just subtract to compute the dir.
2756 if (pts[(index + 1) % n].fY == pts[index].fY) {
2757 int maxIndex;
2758 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002759 if (minIndex == maxIndex) {
2760 goto TRY_CROSSPROD;
2761 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002762 SkASSERT(pts[minIndex].fY == pts[index].fY);
2763 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2764 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2765 // we just subtract the indices, and let that auto-convert to
2766 // SkScalar, since we just want - or + to signal the direction.
2767 cross = minIndex - maxIndex;
2768 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002769 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002770 // Find a next and prev index to use for the cross-product test,
2771 // but we try to find pts that form non-zero vectors from pts[index]
2772 //
2773 // Its possible that we can't find two non-degenerate vectors, so
2774 // we have to guard our search (e.g. all the pts could be in the
2775 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002776
reed@google.comc1ea60a2012-01-31 15:15:36 +00002777 // we pass n - 1 instead of -1 so we don't foul up % operator by
2778 // passing it a negative LH argument.
2779 int prev = find_diff_pt(pts, index, n, n - 1);
2780 if (prev == index) {
2781 // completely degenerate, skip to next contour
2782 continue;
2783 }
2784 int next = find_diff_pt(pts, index, n, 1);
2785 SkASSERT(next != index);
2786 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002787 // if we get a zero and the points are horizontal, then we look at the spread in
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002788 // x-direction. We really should continue to walk away from the degeneracy until
2789 // there is a divergence.
2790 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002791 // construct the subtract so we get the correct Direction below
2792 cross = pts[index].fX - pts[next].fX;
2793 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002794 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002795
reed@google.comac8543f2012-01-30 20:51:25 +00002796 if (cross) {
2797 // record our best guess so far
2798 ymax = pts[index].fY;
2799 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002800 }
reed@google.com69a99432012-01-10 18:00:10 +00002801 }
2802 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002803 if (ymaxCross) {
2804 crossToDir(ymaxCross, dir);
2805 fDirection = *dir;
2806 return true;
2807 } else {
2808 return false;
2809 }
reed@google.comac8543f2012-01-30 20:51:25 +00002810}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002811
2812///////////////////////////////////////////////////////////////////////////////
2813
2814static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2815 SkScalar D, SkScalar t) {
2816 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2817}
2818
2819static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2820 SkScalar t) {
2821 SkScalar A = c3 + 3*(c1 - c2) - c0;
2822 SkScalar B = 3*(c2 - c1 - c1 + c0);
2823 SkScalar C = 3*(c1 - c0);
2824 SkScalar D = c0;
2825 return eval_cubic_coeff(A, B, C, D, t);
2826}
2827
2828/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2829 t value such that cubic(t) = target
2830 */
2831static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2832 SkScalar target, SkScalar* t) {
2833 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2834 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 SkScalar D = c0 - target;
2837 SkScalar A = c3 + 3*(c1 - c2) - c0;
2838 SkScalar B = 3*(c2 - c1 - c1 + c0);
2839 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2842 SkScalar minT = 0;
2843 SkScalar maxT = SK_Scalar1;
2844 SkScalar mid;
2845 int i;
2846 for (i = 0; i < 16; i++) {
2847 mid = SkScalarAve(minT, maxT);
2848 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2849 if (delta < 0) {
2850 minT = mid;
2851 delta = -delta;
2852 } else {
2853 maxT = mid;
2854 }
2855 if (delta < TOLERANCE) {
2856 break;
2857 }
2858 }
2859 *t = mid;
2860 return true;
2861}
2862
2863template <size_t N> static void find_minmax(const SkPoint pts[],
2864 SkScalar* minPtr, SkScalar* maxPtr) {
2865 SkScalar min, max;
2866 min = max = pts[0].fX;
2867 for (size_t i = 1; i < N; ++i) {
2868 min = SkMinScalar(min, pts[i].fX);
2869 max = SkMaxScalar(max, pts[i].fX);
2870 }
2871 *minPtr = min;
2872 *maxPtr = max;
2873}
2874
2875static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2876 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002877
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 int dir = 1;
2879 if (pts[0].fY > pts[3].fY) {
2880 storage[0] = pts[3];
2881 storage[1] = pts[2];
2882 storage[2] = pts[1];
2883 storage[3] = pts[0];
2884 pts = storage;
2885 dir = -1;
2886 }
2887 if (y < pts[0].fY || y >= pts[3].fY) {
2888 return 0;
2889 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 // quickreject or quickaccept
2892 SkScalar min, max;
2893 find_minmax<4>(pts, &min, &max);
2894 if (x < min) {
2895 return 0;
2896 }
2897 if (x > max) {
2898 return dir;
2899 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002900
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901 // compute the actual x(t) value
2902 SkScalar t, xt;
2903 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2904 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2905 } else {
2906 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2907 xt = y < mid ? pts[0].fX : pts[3].fX;
2908 }
2909 return xt < x ? dir : 0;
2910}
2911
2912static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2913 SkPoint dst[10];
2914 int n = SkChopCubicAtYExtrema(pts, dst);
2915 int w = 0;
2916 for (int i = 0; i <= n; ++i) {
2917 w += winding_mono_cubic(&dst[i * 3], x, y);
2918 }
2919 return w;
2920}
2921
2922static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2923 SkScalar y0 = pts[0].fY;
2924 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002925
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002926 int dir = 1;
2927 if (y0 > y2) {
2928 SkTSwap(y0, y2);
2929 dir = -1;
2930 }
2931 if (y < y0 || y >= y2) {
2932 return 0;
2933 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002934
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 // bounds check on X (not required. is it faster?)
2936#if 0
2937 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2938 return 0;
2939 }
2940#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002941
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002942 SkScalar roots[2];
2943 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2944 2 * (pts[1].fY - pts[0].fY),
2945 pts[0].fY - y,
2946 roots);
2947 SkASSERT(n <= 1);
2948 SkScalar xt;
2949 if (0 == n) {
2950 SkScalar mid = SkScalarAve(y0, y2);
2951 // Need [0] and [2] if dir == 1
2952 // and [2] and [0] if dir == -1
2953 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2954 } else {
2955 SkScalar t = roots[0];
2956 SkScalar C = pts[0].fX;
2957 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2958 SkScalar B = 2 * (pts[1].fX - C);
2959 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2960 }
2961 return xt < x ? dir : 0;
2962}
2963
2964static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2965 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2966 if (y0 == y1) {
2967 return true;
2968 }
2969 if (y0 < y1) {
2970 return y1 <= y2;
2971 } else {
2972 return y1 >= y2;
2973 }
2974}
2975
2976static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2977 SkPoint dst[5];
2978 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002979
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2981 n = SkChopQuadAtYExtrema(pts, dst);
2982 pts = dst;
2983 }
2984 int w = winding_mono_quad(pts, x, y);
2985 if (n > 0) {
2986 w += winding_mono_quad(&pts[2], x, y);
2987 }
2988 return w;
2989}
2990
2991static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2992 SkScalar x0 = pts[0].fX;
2993 SkScalar y0 = pts[0].fY;
2994 SkScalar x1 = pts[1].fX;
2995 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002996
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002998
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 int dir = 1;
3000 if (y0 > y1) {
3001 SkTSwap(y0, y1);
3002 dir = -1;
3003 }
3004 if (y < y0 || y >= y1) {
3005 return 0;
3006 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003007
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
3009 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003010
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 if (SkScalarSignAsInt(cross) == dir) {
3012 dir = 0;
3013 }
3014 return dir;
3015}
3016
3017bool SkPath::contains(SkScalar x, SkScalar y) const {
3018 bool isInverse = this->isInverseFillType();
3019 if (this->isEmpty()) {
3020 return isInverse;
3021 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003022
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003023 const SkRect& bounds = this->getBounds();
3024 if (!bounds.contains(x, y)) {
3025 return isInverse;
3026 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003027
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 SkPath::Iter iter(*this, true);
3029 bool done = false;
3030 int w = 0;
3031 do {
3032 SkPoint pts[4];
3033 switch (iter.next(pts, false)) {
3034 case SkPath::kMove_Verb:
3035 case SkPath::kClose_Verb:
3036 break;
3037 case SkPath::kLine_Verb:
3038 w += winding_line(pts, x, y);
3039 break;
3040 case SkPath::kQuad_Verb:
3041 w += winding_quad(pts, x, y);
3042 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003043 case SkPath::kConic_Verb:
3044 SkASSERT(0);
3045 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003046 case SkPath::kCubic_Verb:
3047 w += winding_cubic(pts, x, y);
3048 break;
3049 case SkPath::kDone_Verb:
3050 done = true;
3051 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003052 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003053 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003054
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003055 switch (this->getFillType()) {
3056 case SkPath::kEvenOdd_FillType:
3057 case SkPath::kInverseEvenOdd_FillType:
3058 w &= 1;
3059 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003060 default:
3061 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003062 }
3063 return SkToBool(w);
3064}