blob: 35f4772b54ff67a7746bd03306075181e3c5a8b7 [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
bungeman@google.coma5809a32013-06-21 15:13:34 +0000222#ifdef SK_BUILD_FOR_ANDROID
223 , fGenerationID(0)
224#endif
225{
226 this->resetFields();
227}
228
229void SkPath::resetFields() {
230 //fPathRef is assumed to have been emptied by the caller.
231 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
232 fFillType = kWinding_FillType;
233 fSegmentMask = 0;
234 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000235 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000236 fDirection = kUnknown_Direction;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000237 fIsFinite = false;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000238 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000239#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000240 GEN_ID_INC;
djsollen@google.come63793a2012-03-21 15:39:03 +0000241 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000242#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000243}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
bungeman@google.coma5809a32013-06-21 15:13:34 +0000245SkPath::SkPath(const SkPath& that)
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000246#if SK_DEBUG_PATH_REF
247 : fPathRef(this)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000248#else
249 : fPathRef(SkRef(that.fPathRef.get()))
250#endif
251#ifdef SK_BUILD_FOR_ANDROID
252 , fGenerationID(0)
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000253#endif
254{
bungeman@google.coma5809a32013-06-21 15:13:34 +0000255 this->copyFields(that);
256 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257}
258
259SkPath::~SkPath() {
260 SkDEBUGCODE(this->validate();)
261}
262
bungeman@google.coma5809a32013-06-21 15:13:34 +0000263SkPath& SkPath::operator=(const SkPath& that) {
264 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265
bungeman@google.coma5809a32013-06-21 15:13:34 +0000266 if (this != &that) {
267 fPathRef.reset(SkRef(that.fPathRef.get()));
268 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 }
270 SkDEBUGCODE(this->validate();)
271 return *this;
272}
273
bungeman@google.coma5809a32013-06-21 15:13:34 +0000274void SkPath::copyFields(const SkPath& that) {
275 //fPathRef is assumed to have been set by the caller.
276 fBounds = that.fBounds;
277 fLastMoveToIndex = that.fLastMoveToIndex;
278 fFillType = that.fFillType;
279 fSegmentMask = that.fSegmentMask;
280 fBoundsIsDirty = that.fBoundsIsDirty;
281 fConvexity = that.fConvexity;
282 fDirection = that.fDirection;
283 fIsFinite = that.fIsFinite;
284 fIsOval = that.fIsOval;
285#ifdef SK_BUILD_FOR_ANDROID
286 GEN_ID_INC;
287 fSourcePath = NULL;
288#endif
289}
290
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000291SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000292 // note: don't need to look at isConvex or bounds, since just comparing the
293 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000294
295 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
296 // since it is only a cache of info in the fVerbs, but its a fast way to
297 // notice a difference
298
reed@android.com3abec1d2009-03-02 05:36:20 +0000299 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000300 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000301 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000302}
303
bungeman@google.coma5809a32013-06-21 15:13:34 +0000304void SkPath::swap(SkPath& that) {
305 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306
bungeman@google.coma5809a32013-06-21 15:13:34 +0000307 if (this != &that) {
308 fPathRef.swap(&that.fPathRef);
309 SkTSwap<SkRect>(fBounds, that.fBounds);
310 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
311 SkTSwap<uint8_t>(fFillType, that.fFillType);
312 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
313 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
314 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
315 SkTSwap<uint8_t>(fDirection, that.fDirection);
316 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
317 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000318 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000319 GEN_ID_PTR_INC(&that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 }
321}
322
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000323static inline bool check_edge_against_rect(const SkPoint& p0,
324 const SkPoint& p1,
325 const SkRect& rect,
326 SkPath::Direction dir) {
327 const SkPoint* edgeBegin;
328 SkVector v;
329 if (SkPath::kCW_Direction == dir) {
330 v = p1 - p0;
331 edgeBegin = &p0;
332 } else {
333 v = p0 - p1;
334 edgeBegin = &p1;
335 }
336 if (v.fX || v.fY) {
337 // check the cross product of v with the vec from edgeBegin to each rect corner
338 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
339 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
340 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
341 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
342 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
343 return false;
344 }
345 }
346 return true;
347}
348
349bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
350 // This only handles non-degenerate convex paths currently.
351 if (kConvex_Convexity != this->getConvexity()) {
352 return false;
353 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000354
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000355 Direction direction;
356 if (!this->cheapComputeDirection(&direction)) {
357 return false;
358 }
359
360 SkPoint firstPt;
361 SkPoint prevPt;
362 RawIter iter(*this);
363 SkPath::Verb verb;
364 SkPoint pts[4];
365 SkDEBUGCODE(int moveCnt = 0;)
366
367 while ((verb = iter.next(pts)) != kDone_Verb) {
368 int nextPt = -1;
369 switch (verb) {
370 case kMove_Verb:
371 SkASSERT(!moveCnt);
372 SkDEBUGCODE(++moveCnt);
373 firstPt = prevPt = pts[0];
374 break;
375 case kLine_Verb:
376 nextPt = 1;
377 SkASSERT(moveCnt);
378 break;
379 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000380 case kConic_Verb:
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000381 SkASSERT(moveCnt);
382 nextPt = 2;
383 break;
384 case kCubic_Verb:
385 SkASSERT(moveCnt);
386 nextPt = 3;
387 break;
388 case kClose_Verb:
389 break;
390 default:
391 SkDEBUGFAIL("unknown verb");
392 }
393 if (-1 != nextPt) {
394 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
395 return false;
396 }
397 prevPt = pts[nextPt];
398 }
399 }
400
401 return check_edge_against_rect(prevPt, firstPt, rect, direction);
402}
403
djsollen@google.com56c69772011-11-08 19:00:26 +0000404#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000405uint32_t SkPath::getGenerationID() const {
406 return fGenerationID;
407}
djsollen@google.come63793a2012-03-21 15:39:03 +0000408
409const SkPath* SkPath::getSourcePath() const {
410 return fSourcePath;
411}
412
413void SkPath::setSourcePath(const SkPath* path) {
414 fSourcePath = path;
415}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000416#endif
417
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418void SkPath::reset() {
419 SkDEBUGCODE(this->validate();)
420
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000421 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000422 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423}
424
425void SkPath::rewind() {
426 SkDEBUGCODE(this->validate();)
427
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000428 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000429 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430}
431
432bool SkPath::isEmpty() const {
433 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000434 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435}
436
reed@google.com7e6c4d12012-05-10 14:05:43 +0000437bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000438 int verbCount = fPathRef->countVerbs();
439 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000440
reed@google.com7e6c4d12012-05-10 14:05:43 +0000441 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000442 if (kMove_Verb == fPathRef->atVerb(0) &&
443 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000444 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000445 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000446 line[0] = pts[0];
447 line[1] = pts[1];
448 }
449 return true;
450 }
451 }
452 return false;
453}
454
caryclark@google.comf1316942011-07-26 19:54:45 +0000455/*
456 Determines if path is a rect by keeping track of changes in direction
457 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000458
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 The direction is computed such that:
460 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000461 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000463 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000464
caryclark@google.comf1316942011-07-26 19:54:45 +0000465A rectangle cycles up/right/down/left or up/left/down/right.
466
467The test fails if:
468 The path is closed, and followed by a line.
469 A second move creates a new endpoint.
470 A diagonal line is parsed.
471 There's more than four changes of direction.
472 There's a discontinuity on the line (e.g., a move in the middle)
473 The line reverses direction.
474 The rectangle doesn't complete a cycle.
475 The path contains a quadratic or cubic.
476 The path contains fewer than four points.
477 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000478
caryclark@google.comf1316942011-07-26 19:54:45 +0000479It's OK if the path has:
480 Several colinear line segments composing a rectangle side.
481 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000482
caryclark@google.comf1316942011-07-26 19:54:45 +0000483The direction takes advantage of the corners found since opposite sides
484must travel in opposite directions.
485
486FIXME: Allow colinear quads and cubics to be treated like lines.
487FIXME: If the API passes fill-only, return true if the filled stroke
488 is a rectangle, though the caller failed to close the path.
489 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000490bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
491 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 int corners = 0;
493 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000494 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000496 first.set(0, 0);
497 last.set(0, 0);
498 int firstDirection = 0;
499 int lastDirection = 0;
500 int nextDirection = 0;
501 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000503 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
505 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 savePts = pts;
508 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 autoClose = true;
510 case kLine_Verb: {
511 SkScalar left = last.fX;
512 SkScalar top = last.fY;
513 SkScalar right = pts->fX;
514 SkScalar bottom = pts->fY;
515 ++pts;
516 if (left != right && top != bottom) {
517 return false; // diagonal
518 }
519 if (left == right && top == bottom) {
520 break; // single point on side OK
521 }
522 nextDirection = (left != right) << 0 |
523 (left < right || top < bottom) << 1;
524 if (0 == corners) {
525 firstDirection = nextDirection;
526 first = last;
527 last = pts[-1];
528 corners = 1;
529 closedOrMoved = false;
530 break;
531 }
532 if (closedOrMoved) {
533 return false; // closed followed by a line
534 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000535 if (autoClose && nextDirection == firstDirection) {
536 break; // colinear with first
537 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000538 closedOrMoved = autoClose;
539 if (lastDirection != nextDirection) {
540 if (++corners > 4) {
541 return false; // too many direction changes
542 }
543 }
544 last = pts[-1];
545 if (lastDirection == nextDirection) {
546 break; // colinear segment
547 }
548 // Possible values for corners are 2, 3, and 4.
549 // When corners == 3, nextDirection opposes firstDirection.
550 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000551 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000552 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
553 if ((directionCycle ^ turn) != nextDirection) {
554 return false; // direction didn't follow cycle
555 }
556 break;
557 }
558 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000559 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 case kCubic_Verb:
561 return false; // quadratic, cubic not allowed
562 case kMove_Verb:
563 last = *pts++;
564 closedOrMoved = true;
565 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000566 default:
567 SkASSERT(!"unexpected verb");
568 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000569 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000571 lastDirection = nextDirection;
572 }
573 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000574 bool result = 4 == corners && (first == last || autoClose);
575 if (savePts) {
576 *ptsPtr = savePts;
577 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000578 if (result && isClosed) {
579 *isClosed = autoClose;
580 }
581 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000582 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000583 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000584 return result;
585}
586
reed@google.comda2b21f2013-06-21 17:32:32 +0000587bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 SkDEBUGCODE(this->validate();)
589 int currVerb = 0;
590 const SkPoint* pts = fPathRef->points();
reed@google.comda2b21f2013-06-21 17:32:32 +0000591 const SkPoint* first = pts;
592 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
593 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000594 }
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000595
reed@google.comda2b21f2013-06-21 17:32:32 +0000596 if (rect) {
597 rect->set(first, SkToS32(pts - first));
598 }
599 return true;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000600}
601
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000602bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000603 SkDEBUGCODE(this->validate();)
604 int currVerb = 0;
605 const SkPoint* pts = fPathRef->points();
606 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 Direction testDirs[2];
608 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 return false;
610 }
611 const SkPoint* last = pts;
612 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000613 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000614 testRects[0].set(first, SkToS32(last - first));
615 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000616 if (testRects[0].contains(testRects[1])) {
617 if (rects) {
618 rects[0] = testRects[0];
619 rects[1] = testRects[1];
620 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000621 if (dirs) {
622 dirs[0] = testDirs[0];
623 dirs[1] = testDirs[1];
624 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000625 return true;
626 }
627 if (testRects[1].contains(testRects[0])) {
628 if (rects) {
629 rects[0] = testRects[1];
630 rects[1] = testRects[0];
631 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000632 if (dirs) {
633 dirs[0] = testDirs[1];
634 dirs[1] = testDirs[0];
635 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000636 return true;
637 }
638 }
639 return false;
640}
641
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642int SkPath::countPoints() const {
643 return fPathRef->countPoints();
644}
645
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000646int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 SkDEBUGCODE(this->validate();)
648
649 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000650 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 int count = SkMin32(max, fPathRef->countPoints());
652 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
653 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654}
655
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000656SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
658 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000659 }
660 return SkPoint::Make(0, 0);
661}
662
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663int SkPath::countVerbs() const {
664 return fPathRef->countVerbs();
665}
666
667static inline void copy_verbs_reverse(uint8_t* inorderDst,
668 const uint8_t* reversedSrc,
669 int count) {
670 for (int i = 0; i < count; ++i) {
671 inorderDst[i] = reversedSrc[~i];
672 }
673}
674
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000675int SkPath::getVerbs(uint8_t dst[], int max) const {
676 SkDEBUGCODE(this->validate();)
677
678 SkASSERT(max >= 0);
679 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 int count = SkMin32(max, fPathRef->countVerbs());
681 copy_verbs_reverse(dst, fPathRef->verbs(), count);
682 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000683}
684
reed@google.com294dd7b2011-10-11 11:58:32 +0000685bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000689 if (count > 0) {
690 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000693 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000694 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000695 if (lastPt) {
696 lastPt->set(0, 0);
697 }
698 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699}
700
701void SkPath::setLastPt(SkScalar x, SkScalar y) {
702 SkDEBUGCODE(this->validate();)
703
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000704 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 if (count == 0) {
706 this->moveTo(x, y);
707 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000708 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000709 SkPathRef::Editor ed(&fPathRef);
710 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000711 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712 }
713}
714
reed@android.comd252db02009-04-01 18:31:44 +0000715void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000717 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000720 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721}
722
reed@google.com04863fa2011-05-15 04:08:24 +0000723void SkPath::setConvexity(Convexity c) {
724 if (fConvexity != c) {
725 fConvexity = c;
726 GEN_ID_INC;
727 }
728}
729
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730//////////////////////////////////////////////////////////////////////////////
731// Construction methods
732
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000733#define DIRTY_AFTER_EDIT \
734 do { \
735 fBoundsIsDirty = true; \
736 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000737 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000738 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000739 } while (0)
740
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000741#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
742 do { \
743 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000744 } while (0)
745
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746void SkPath::incReserve(U16CPU inc) {
747 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000748 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749 SkDEBUGCODE(this->validate();)
750}
751
752void SkPath::moveTo(SkScalar x, SkScalar y) {
753 SkDEBUGCODE(this->validate();)
754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756
reed@google.comd335d1d2012-01-12 18:17:11 +0000757 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000758 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000760 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000762 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000763 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764}
765
766void SkPath::rMoveTo(SkScalar x, SkScalar y) {
767 SkPoint pt;
768 this->getLastPt(&pt);
769 this->moveTo(pt.fX + x, pt.fY + y);
770}
771
reed@google.comd335d1d2012-01-12 18:17:11 +0000772void SkPath::injectMoveToIfNeeded() {
773 if (fLastMoveToIndex < 0) {
774 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000775 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000776 x = y = 0;
777 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000778 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000779 x = pt.fX;
780 y = pt.fY;
781 }
782 this->moveTo(x, y);
783 }
784}
785
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786void SkPath::lineTo(SkScalar x, SkScalar y) {
787 SkDEBUGCODE(this->validate();)
788
reed@google.comd335d1d2012-01-12 18:17:11 +0000789 this->injectMoveToIfNeeded();
790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000791 SkPathRef::Editor ed(&fPathRef);
792 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000793 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000795 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000796 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797}
798
799void SkPath::rLineTo(SkScalar x, SkScalar y) {
800 SkPoint pt;
801 this->getLastPt(&pt);
802 this->lineTo(pt.fX + x, pt.fY + y);
803}
804
805void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
806 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000807
reed@google.comd335d1d2012-01-12 18:17:11 +0000808 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000809
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000810 SkPathRef::Editor ed(&fPathRef);
811 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 pts[0].set(x1, y1);
813 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000814 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000815
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000816 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000817 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818}
819
820void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
821 SkPoint pt;
822 this->getLastPt(&pt);
823 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
824}
825
reed@google.com277c3f82013-05-31 15:17:50 +0000826void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
827 SkScalar w) {
828 // check for <= 0 or NaN with this test
829 if (!(w > 0)) {
830 this->lineTo(x2, y2);
831 } else if (!SkScalarIsFinite(w)) {
832 this->lineTo(x1, y1);
833 this->lineTo(x2, y2);
834 } else if (SK_Scalar1 == w) {
835 this->quadTo(x1, y1, x2, y2);
836 } else {
837 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000838
reed@google.com277c3f82013-05-31 15:17:50 +0000839 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000840
reed@google.com277c3f82013-05-31 15:17:50 +0000841 SkPathRef::Editor ed(&fPathRef);
842 SkPoint* pts = ed.growForConic(w);
843 pts[0].set(x1, y1);
844 pts[1].set(x2, y2);
845 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000846
reed@google.com277c3f82013-05-31 15:17:50 +0000847 GEN_ID_INC;
848 DIRTY_AFTER_EDIT;
849 }
850}
851
852void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
853 SkScalar w) {
854 SkPoint pt;
855 this->getLastPt(&pt);
856 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
857}
858
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
860 SkScalar x3, SkScalar y3) {
861 SkDEBUGCODE(this->validate();)
862
reed@google.comd335d1d2012-01-12 18:17:11 +0000863 this->injectMoveToIfNeeded();
864
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 SkPathRef::Editor ed(&fPathRef);
866 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 pts[0].set(x1, y1);
868 pts[1].set(x2, y2);
869 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000870 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000872 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000873 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874}
875
876void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
877 SkScalar x3, SkScalar y3) {
878 SkPoint pt;
879 this->getLastPt(&pt);
880 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
881 pt.fX + x3, pt.fY + y3);
882}
883
884void SkPath::close() {
885 SkDEBUGCODE(this->validate();)
886
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000889 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 case kLine_Verb:
891 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000892 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000894 case kMove_Verb: {
895 SkPathRef::Editor ed(&fPathRef);
896 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000897 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000899 }
reed@google.com277c3f82013-05-31 15:17:50 +0000900 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000901 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000902 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000903 default:
904 SkASSERT(!"unexpected verb");
905 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 }
907 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000908
909 // signal that we need a moveTo to follow us (unless we're done)
910#if 0
911 if (fLastMoveToIndex >= 0) {
912 fLastMoveToIndex = ~fLastMoveToIndex;
913 }
914#else
915 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
916#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917}
918
919///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000920
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000921static void assert_known_direction(int dir) {
922 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
923}
924
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925void SkPath::addRect(const SkRect& rect, Direction dir) {
926 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
927}
928
929void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
930 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000931 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000932 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
933 SkAutoDisableDirectionCheck addc(this);
934
reed@android.com8a1c16f2008-12-17 15:59:43 +0000935 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
936
937 this->incReserve(5);
938
939 this->moveTo(left, top);
940 if (dir == kCCW_Direction) {
941 this->lineTo(left, bottom);
942 this->lineTo(right, bottom);
943 this->lineTo(right, top);
944 } else {
945 this->lineTo(right, top);
946 this->lineTo(right, bottom);
947 this->lineTo(left, bottom);
948 }
949 this->close();
950}
951
reed@google.com744faba2012-05-29 19:54:52 +0000952void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
953 SkDEBUGCODE(this->validate();)
954 if (count <= 0) {
955 return;
956 }
957
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000958 SkPathRef::Editor ed(&fPathRef);
959 fLastMoveToIndex = ed.pathRef()->countPoints();
960 uint8_t* vb;
961 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000962 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000963 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000964
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000965 memcpy(p, pts, count * sizeof(SkPoint));
966 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000967 if (count > 1) {
968 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
969 // be 0, the compiler will remove the test/branch entirely.
970 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000971 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000972 } else {
973 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000974 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000975 }
976 }
977 fSegmentMask |= kLine_SegmentMask;
978 }
979 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000980 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000981 }
982
983 GEN_ID_INC;
984 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000985 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000986}
987
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988static void add_corner_arc(SkPath* path, const SkRect& rect,
989 SkScalar rx, SkScalar ry, int startAngle,
990 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000991 // These two asserts are not sufficient, since really we want to know
992 // that the pair of radii (e.g. left and right, or top and bottom) sum
993 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000994 // these conservative asserts.
995 SkASSERT(0 <= rx && rx <= rect.width());
996 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000997
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998 SkRect r;
999 r.set(-rx, -ry, rx, ry);
1000
1001 switch (startAngle) {
1002 case 0:
1003 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1004 break;
1005 case 90:
1006 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1007 break;
1008 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1009 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001010 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 }
reed@google.comabf15c12011-01-18 20:35:51 +00001012
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013 SkScalar start = SkIntToScalar(startAngle);
1014 SkScalar sweep = SkIntToScalar(90);
1015 if (SkPath::kCCW_Direction == dir) {
1016 start += sweep;
1017 sweep = -sweep;
1018 }
reed@google.comabf15c12011-01-18 20:35:51 +00001019
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 path->arcTo(r, start, sweep, forceMoveTo);
1021}
1022
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001023void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001024 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001025 SkRRect rrect;
1026 rrect.setRectRadii(rect, (const SkVector*) radii);
1027 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028}
1029
reed@google.com4ed0fb72012-12-12 20:48:18 +00001030void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001031 assert_known_direction(dir);
1032
1033 if (rrect.isEmpty()) {
1034 return;
1035 }
1036
reed@google.com4ed0fb72012-12-12 20:48:18 +00001037 const SkRect& bounds = rrect.getBounds();
1038
1039 if (rrect.isRect()) {
1040 this->addRect(bounds, dir);
1041 } else if (rrect.isOval()) {
1042 this->addOval(bounds, dir);
1043 } else if (rrect.isSimple()) {
1044 const SkVector& rad = rrect.getSimpleRadii();
1045 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1046 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001047 SkAutoPathBoundsUpdate apbu(this, bounds);
1048
1049 if (kCW_Direction == dir) {
1050 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1051 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1052 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1053 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1054 } else {
1055 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1056 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1057 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1058 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1059 }
1060 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001061 }
1062}
1063
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001064bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001065 int count = fPathRef->countVerbs();
1066 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1067 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001068 if (*verbs == kLine_Verb ||
1069 *verbs == kQuad_Verb ||
1070 *verbs == kCubic_Verb) {
1071 return false;
1072 }
1073 ++verbs;
1074 }
1075 return true;
1076}
1077
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001078#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1079
1080void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1081 Direction dir) {
1082 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001083
humper@google.com75e3ca12013-04-08 21:44:11 +00001084 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001085 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001086 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001087 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001088 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1089 return;
1090 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001091
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001092 SkScalar w = rect.width();
1093 SkScalar halfW = SkScalarHalf(w);
1094 SkScalar h = rect.height();
1095 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001096
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001097 if (halfW <= 0 || halfH <= 0) {
1098 return;
1099 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001100
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001101 bool skip_hori = rx >= halfW;
1102 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001103
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001104 if (skip_hori && skip_vert) {
1105 this->addOval(rect, dir);
1106 return;
1107 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001108
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001109 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001110
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001111 SkAutoPathBoundsUpdate apbu(this, rect);
1112 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001113
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001114 if (skip_hori) {
1115 rx = halfW;
1116 } else if (skip_vert) {
1117 ry = halfH;
1118 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001119
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001120 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1121 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001122
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001123 this->incReserve(17);
1124 this->moveTo(rect.fRight - rx, rect.fTop);
1125 if (dir == kCCW_Direction) {
1126 if (!skip_hori) {
1127 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1128 }
1129 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1130 rect.fLeft, rect.fTop + ry - sy,
1131 rect.fLeft, rect.fTop + ry); // top-left
1132 if (!skip_vert) {
1133 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1134 }
1135 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1136 rect.fLeft + rx - sx, rect.fBottom,
1137 rect.fLeft + rx, rect.fBottom); // bot-left
1138 if (!skip_hori) {
1139 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1140 }
1141 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1142 rect.fRight, rect.fBottom - ry + sy,
1143 rect.fRight, rect.fBottom - ry); // bot-right
1144 if (!skip_vert) {
1145 this->lineTo(rect.fRight, rect.fTop + ry);
1146 }
1147 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1148 rect.fRight - rx + sx, rect.fTop,
1149 rect.fRight - rx, rect.fTop); // top-right
1150 } else {
1151 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1152 rect.fRight, rect.fTop + ry - sy,
1153 rect.fRight, rect.fTop + ry); // top-right
1154 if (!skip_vert) {
1155 this->lineTo(rect.fRight, rect.fBottom - ry);
1156 }
1157 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1158 rect.fRight - rx + sx, rect.fBottom,
1159 rect.fRight - rx, rect.fBottom); // bot-right
1160 if (!skip_hori) {
1161 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1162 }
1163 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1164 rect.fLeft, rect.fBottom - ry + sy,
1165 rect.fLeft, rect.fBottom - ry); // bot-left
1166 if (!skip_vert) {
1167 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1168 }
1169 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1170 rect.fLeft + rx - sx, rect.fTop,
1171 rect.fLeft + rx, rect.fTop); // top-left
1172 if (!skip_hori) {
1173 this->lineTo(rect.fRight - rx, rect.fTop); // top
1174 }
1175 }
1176 this->close();
1177}
1178
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001180 assert_known_direction(dir);
1181
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001182 /* If addOval() is called after previous moveTo(),
1183 this path is still marked as an oval. This is used to
1184 fit into WebKit's calling sequences.
1185 We can't simply check isEmpty() in this case, as additional
1186 moveTo() would mark the path non empty.
1187 */
1188 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001189 if (fIsOval) {
1190 fDirection = dir;
1191 } else {
1192 fDirection = kUnknown_Direction;
1193 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194
1195 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001196 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 SkAutoPathBoundsUpdate apbu(this, oval);
1199
1200 SkScalar cx = oval.centerX();
1201 SkScalar cy = oval.centerY();
1202 SkScalar rx = SkScalarHalf(oval.width());
1203 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1206 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1207 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1208 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1209
1210 /*
1211 To handle imprecision in computing the center and radii, we revert to
1212 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1213 to ensure that we don't exceed the oval's bounds *ever*, since we want
1214 to use oval for our fast-bounds, rather than have to recompute it.
1215 */
1216 const SkScalar L = oval.fLeft; // cx - rx
1217 const SkScalar T = oval.fTop; // cy - ry
1218 const SkScalar R = oval.fRight; // cx + rx
1219 const SkScalar B = oval.fBottom; // cy + ry
1220
1221 this->incReserve(17); // 8 quads + close
1222 this->moveTo(R, cy);
1223 if (dir == kCCW_Direction) {
1224 this->quadTo( R, cy - sy, cx + mx, cy - my);
1225 this->quadTo(cx + sx, T, cx , T);
1226 this->quadTo(cx - sx, T, cx - mx, cy - my);
1227 this->quadTo( L, cy - sy, L, cy );
1228 this->quadTo( L, cy + sy, cx - mx, cy + my);
1229 this->quadTo(cx - sx, B, cx , B);
1230 this->quadTo(cx + sx, B, cx + mx, cy + my);
1231 this->quadTo( R, cy + sy, R, cy );
1232 } else {
1233 this->quadTo( R, cy + sy, cx + mx, cy + my);
1234 this->quadTo(cx + sx, B, cx , B);
1235 this->quadTo(cx - sx, B, cx - mx, cy + my);
1236 this->quadTo( L, cy + sy, L, cy );
1237 this->quadTo( L, cy - sy, cx - mx, cy - my);
1238 this->quadTo(cx - sx, T, cx , T);
1239 this->quadTo(cx + sx, T, cx + mx, cy - my);
1240 this->quadTo( R, cy - sy, R, cy );
1241 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 this->close();
1243}
1244
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001245bool SkPath::isOval(SkRect* rect) const {
1246 if (fIsOval && rect) {
1247 *rect = getBounds();
1248 }
1249
1250 return fIsOval;
1251}
1252
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1254 if (r > 0) {
1255 SkRect rect;
1256 rect.set(x - r, y - r, x + r, y + r);
1257 this->addOval(rect, dir);
1258 }
1259}
1260
1261#include "SkGeometry.h"
1262
1263static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1264 SkScalar sweepAngle,
1265 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001266
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001267 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001268 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1269 // Chrome uses this path to move into and out of ovals. If not
1270 // treated as a special case the moves can distort the oval's
1271 // bounding box (and break the circle special case).
1272 pts[0].set(oval.fRight, oval.centerY());
1273 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001274 } else if (0 == oval.width() && 0 == oval.height()) {
1275 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001276 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001277 // a rect.
1278 // TODO: optimizing the case where only one of width or height is zero
1279 // should also be considered. This case, however, doesn't seem to be
1280 // as common as the single point case.
1281 pts[0].set(oval.fRight, oval.fTop);
1282 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001283 }
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285 SkVector start, stop;
1286
1287 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1288 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1289 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001290
1291 /* If the sweep angle is nearly (but less than) 360, then due to precision
1292 loss in radians-conversion and/or sin/cos, we may end up with coincident
1293 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1294 of drawing a nearly complete circle (good).
1295 e.g. canvas.drawArc(0, 359.99, ...)
1296 -vs- canvas.drawArc(0, 359.9, ...)
1297 We try to detect this edge case, and tweak the stop vector
1298 */
1299 if (start == stop) {
1300 SkScalar sw = SkScalarAbs(sweepAngle);
1301 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1302 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1303 // make a guess at a tiny angle (in radians) to tweak by
1304 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1305 // not sure how much will be enough, so we use a loop
1306 do {
1307 stopRad -= deltaRad;
1308 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1309 } while (start == stop);
1310 }
1311 }
1312
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1316 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 return SkBuildQuadArc(start, stop,
1319 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1320 &matrix, pts);
1321}
1322
1323void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1324 bool forceMoveTo) {
1325 if (oval.width() < 0 || oval.height() < 0) {
1326 return;
1327 }
1328
1329 SkPoint pts[kSkBuildQuadArcStorage];
1330 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1331 SkASSERT((count & 1) == 1);
1332
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001333 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 forceMoveTo = true;
1335 }
1336 this->incReserve(count);
1337 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1338 for (int i = 1; i < count; i += 2) {
1339 this->quadTo(pts[i], pts[i+1]);
1340 }
1341}
1342
1343void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1344 SkScalar sweepAngle) {
1345 if (oval.isEmpty() || 0 == sweepAngle) {
1346 return;
1347 }
1348
1349 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1350
1351 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1352 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1353 return;
1354 }
1355
1356 SkPoint pts[kSkBuildQuadArcStorage];
1357 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1358
1359 this->incReserve(count);
1360 this->moveTo(pts[0]);
1361 for (int i = 1; i < count; i += 2) {
1362 this->quadTo(pts[i], pts[i+1]);
1363 }
1364}
1365
1366/*
1367 Need to handle the case when the angle is sharp, and our computed end-points
1368 for the arc go behind pt1 and/or p2...
1369*/
1370void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1371 SkScalar radius) {
1372 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001373
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 // need to know our prev pt so we can construct tangent vectors
1375 {
1376 SkPoint start;
1377 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001378 // Handle degenerate cases by adding a line to the first point and
1379 // bailing out.
1380 if ((x1 == start.fX && y1 == start.fY) ||
1381 (x1 == x2 && y1 == y2) ||
1382 radius == 0) {
1383 this->lineTo(x1, y1);
1384 return;
1385 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 before.setNormalize(x1 - start.fX, y1 - start.fY);
1387 after.setNormalize(x2 - x1, y2 - y1);
1388 }
reed@google.comabf15c12011-01-18 20:35:51 +00001389
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390 SkScalar cosh = SkPoint::DotProduct(before, after);
1391 SkScalar sinh = SkPoint::CrossProduct(before, after);
1392
1393 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001394 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 return;
1396 }
reed@google.comabf15c12011-01-18 20:35:51 +00001397
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1399 if (dist < 0) {
1400 dist = -dist;
1401 }
1402
1403 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1404 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1405 SkRotationDirection arcDir;
1406
1407 // now turn before/after into normals
1408 if (sinh > 0) {
1409 before.rotateCCW();
1410 after.rotateCCW();
1411 arcDir = kCW_SkRotationDirection;
1412 } else {
1413 before.rotateCW();
1414 after.rotateCW();
1415 arcDir = kCCW_SkRotationDirection;
1416 }
1417
1418 SkMatrix matrix;
1419 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001420
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421 matrix.setScale(radius, radius);
1422 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1423 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001424
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001426
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 this->incReserve(count);
1428 // [xx,yy] == pts[0]
1429 this->lineTo(xx, yy);
1430 for (int i = 1; i < count; i += 2) {
1431 this->quadTo(pts[i], pts[i+1]);
1432 }
1433}
1434
1435///////////////////////////////////////////////////////////////////////////////
1436
1437void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1438 SkMatrix matrix;
1439
1440 matrix.setTranslate(dx, dy);
1441 this->addPath(path, matrix);
1442}
1443
1444void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001445 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001447 fIsOval = false;
1448
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001449 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 SkPoint pts[4];
1451 Verb verb;
1452
1453 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1454
1455 while ((verb = iter.next(pts)) != kDone_Verb) {
1456 switch (verb) {
1457 case kMove_Verb:
1458 proc(matrix, &pts[0], &pts[0], 1);
1459 this->moveTo(pts[0]);
1460 break;
1461 case kLine_Verb:
1462 proc(matrix, &pts[1], &pts[1], 1);
1463 this->lineTo(pts[1]);
1464 break;
1465 case kQuad_Verb:
1466 proc(matrix, &pts[1], &pts[1], 2);
1467 this->quadTo(pts[1], pts[2]);
1468 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001469 case kConic_Verb:
1470 proc(matrix, &pts[1], &pts[1], 2);
1471 this->conicTo(pts[1], pts[2], iter.conicWeight());
1472 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 case kCubic_Verb:
1474 proc(matrix, &pts[1], &pts[1], 3);
1475 this->cubicTo(pts[1], pts[2], pts[3]);
1476 break;
1477 case kClose_Verb:
1478 this->close();
1479 break;
1480 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001481 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 }
1483 }
1484}
1485
1486///////////////////////////////////////////////////////////////////////////////
1487
reed@google.com277c3f82013-05-31 15:17:50 +00001488static int pts_in_verb(unsigned verb) {
1489 static const uint8_t gPtsInVerb[] = {
1490 1, // kMove
1491 1, // kLine
1492 2, // kQuad
1493 2, // kConic
1494 3, // kCubic
1495 0, // kClose
1496 0 // kDone
1497 };
1498
1499 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1500 return gPtsInVerb[verb];
1501}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502
1503// ignore the initial moveto, and stop when the 1st contour ends
1504void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001505 int i, vcount = path.fPathRef->countVerbs();
1506 // exit early if the path is empty, or just has a moveTo.
1507 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 return;
1509 }
1510
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001511 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001513 fIsOval = false;
1514
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001515 const uint8_t* verbs = path.fPathRef->verbs();
1516 // skip the initial moveTo
1517 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001518 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001520 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001522 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kLine_Verb:
1524 this->lineTo(pts[0].fX, pts[0].fY);
1525 break;
1526 case kQuad_Verb:
1527 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1528 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001529 case kConic_Verb:
1530 this->conicTo(pts[0], pts[1], *conicWeight++);
1531 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001533 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 +00001534 break;
1535 case kClose_Verb:
1536 return;
1537 }
reed@google.com277c3f82013-05-31 15:17:50 +00001538 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 }
1540}
1541
1542// ignore the last point of the 1st contour
1543void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 int i, vcount = path.fPathRef->countVerbs();
1545 // exit early if the path is empty, or just has a moveTo.
1546 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 return;
1548 }
1549
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001552 fIsOval = false;
1553
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001554 const uint8_t* verbs = path.fPathRef->verbs();
1555 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001556 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001558 SkASSERT(verbs[~0] == kMove_Verb);
1559 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001560 unsigned v = verbs[~i];
1561 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 if (n == 0) {
1563 break;
1564 }
1565 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001566 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
1568
1569 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 case kLine_Verb:
1572 this->lineTo(pts[-1].fX, pts[-1].fY);
1573 break;
1574 case kQuad_Verb:
1575 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1576 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001577 case kConic_Verb:
1578 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1579 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580 case kCubic_Verb:
1581 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1582 pts[-3].fX, pts[-3].fY);
1583 break;
1584 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001585 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 break;
1587 }
reed@google.com277c3f82013-05-31 15:17:50 +00001588 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 }
1590}
1591
reed@google.com63d73742012-01-10 15:33:12 +00001592void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001593 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001594
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001595 const SkPoint* pts = src.fPathRef->pointsEnd();
1596 // we will iterator through src's verbs backwards
1597 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1598 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001599 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001600
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001601 fIsOval = false;
1602
reed@google.com63d73742012-01-10 15:33:12 +00001603 bool needMove = true;
1604 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001605 while (verbs < verbsEnd) {
1606 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001607 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001608
1609 if (needMove) {
1610 --pts;
1611 this->moveTo(pts->fX, pts->fY);
1612 needMove = false;
1613 }
1614 pts -= n;
1615 switch (v) {
1616 case kMove_Verb:
1617 if (needClose) {
1618 this->close();
1619 needClose = false;
1620 }
1621 needMove = true;
1622 pts += 1; // so we see the point in "if (needMove)" above
1623 break;
1624 case kLine_Verb:
1625 this->lineTo(pts[0]);
1626 break;
1627 case kQuad_Verb:
1628 this->quadTo(pts[1], pts[0]);
1629 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001630 case kConic_Verb:
1631 this->conicTo(pts[1], pts[0], *--conicWeights);
1632 break;
reed@google.com63d73742012-01-10 15:33:12 +00001633 case kCubic_Verb:
1634 this->cubicTo(pts[2], pts[1], pts[0]);
1635 break;
1636 case kClose_Verb:
1637 needClose = true;
1638 break;
1639 default:
1640 SkASSERT(!"unexpected verb");
1641 }
1642 }
1643}
1644
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645///////////////////////////////////////////////////////////////////////////////
1646
1647void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1648 SkMatrix matrix;
1649
1650 matrix.setTranslate(dx, dy);
1651 this->transform(matrix, dst);
1652}
1653
1654#include "SkGeometry.h"
1655
1656static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1657 int level = 2) {
1658 if (--level >= 0) {
1659 SkPoint tmp[5];
1660
1661 SkChopQuadAtHalf(pts, tmp);
1662 subdivide_quad_to(path, &tmp[0], level);
1663 subdivide_quad_to(path, &tmp[2], level);
1664 } else {
1665 path->quadTo(pts[1], pts[2]);
1666 }
1667}
1668
1669static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1670 int level = 2) {
1671 if (--level >= 0) {
1672 SkPoint tmp[7];
1673
1674 SkChopCubicAtHalf(pts, tmp);
1675 subdivide_cubic_to(path, &tmp[0], level);
1676 subdivide_cubic_to(path, &tmp[3], level);
1677 } else {
1678 path->cubicTo(pts[1], pts[2], pts[3]);
1679 }
1680}
1681
1682void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1683 SkDEBUGCODE(this->validate();)
1684 if (dst == NULL) {
1685 dst = (SkPath*)this;
1686 }
1687
tomhudson@google.com8d430182011-06-06 19:11:19 +00001688 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 SkPath tmp;
1690 tmp.fFillType = fFillType;
1691
1692 SkPath::Iter iter(*this, false);
1693 SkPoint pts[4];
1694 SkPath::Verb verb;
1695
reed@google.com4a3b7142012-05-16 17:16:46 +00001696 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 switch (verb) {
1698 case kMove_Verb:
1699 tmp.moveTo(pts[0]);
1700 break;
1701 case kLine_Verb:
1702 tmp.lineTo(pts[1]);
1703 break;
1704 case kQuad_Verb:
1705 subdivide_quad_to(&tmp, pts);
1706 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001707 case kConic_Verb:
1708 SkASSERT(!"TODO: compute new weight");
1709 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1710 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 case kCubic_Verb:
1712 subdivide_cubic_to(&tmp, pts);
1713 break;
1714 case kClose_Verb:
1715 tmp.close();
1716 break;
1717 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001718 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 break;
1720 }
1721 }
1722
1723 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001724 SkPathRef::Editor ed(&dst->fPathRef);
1725 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001726 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001728 /*
1729 * If we're not in perspective, we can transform all of the points at
1730 * once.
1731 *
1732 * Here we also want to optimize bounds, by noting if the bounds are
1733 * already known, and if so, we just transform those as well and mark
1734 * them as "known", rather than force the transformed path to have to
1735 * recompute them.
1736 *
1737 * Special gotchas if the path is effectively empty (<= 1 point) or
1738 * if it is non-finite. In those cases bounds need to stay empty,
1739 * regardless of the matrix.
1740 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001741 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001742 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001743 if (fIsFinite) {
1744 matrix.mapRect(&dst->fBounds, fBounds);
1745 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1746 dst->fBounds.setEmpty();
1747 }
1748 } else {
1749 dst->fIsFinite = false;
1750 dst->fBounds.setEmpty();
1751 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001753 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001754 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 }
1756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001757 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1758
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001761 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001762 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001764
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001765#ifdef SK_BUILD_FOR_ANDROID
1766 if (!matrix.isIdentity()) {
1767 GEN_ID_PTR_INC(dst);
1768 }
1769#endif
1770
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001771 if (kUnknown_Direction == fDirection) {
1772 dst->fDirection = kUnknown_Direction;
1773 } else {
1774 SkScalar det2x2 =
1775 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1776 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1777 if (det2x2 < 0) {
1778 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1779 } else if (det2x2 > 0) {
1780 dst->fDirection = fDirection;
1781 } else {
1782 dst->fDirection = kUnknown_Direction;
1783 }
1784 }
1785
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001786 // It's an oval only if it stays a rect.
1787 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001788
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 SkDEBUGCODE(dst->validate();)
1790 }
1791}
1792
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793///////////////////////////////////////////////////////////////////////////////
1794///////////////////////////////////////////////////////////////////////////////
1795
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001797 kEmptyContour_SegmentState, // The current contour is empty. We may be
1798 // starting processing or we may have just
1799 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001800 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1801 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1802 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803};
1804
1805SkPath::Iter::Iter() {
1806#ifdef SK_DEBUG
1807 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001808 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001811 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812#endif
1813 // need to init enough to make next() harmlessly return kDone_Verb
1814 fVerbs = NULL;
1815 fVerbStop = NULL;
1816 fNeedClose = false;
1817}
1818
1819SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1820 this->setPath(path, forceClose);
1821}
1822
1823void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001824 fPts = path.fPathRef->points();
1825 fVerbs = path.fPathRef->verbs();
1826 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001827 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001829 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 fForceClose = SkToU8(forceClose);
1831 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001832 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833}
1834
1835bool SkPath::Iter::isClosedContour() const {
1836 if (fVerbs == NULL || fVerbs == fVerbStop) {
1837 return false;
1838 }
1839 if (fForceClose) {
1840 return true;
1841 }
1842
1843 const uint8_t* verbs = fVerbs;
1844 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001845
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001846 if (kMove_Verb == *(verbs - 1)) {
1847 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 }
1849
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 while (verbs > stop) {
1851 // verbs points one beyond the current verb, decrement first.
1852 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853 if (kMove_Verb == v) {
1854 break;
1855 }
1856 if (kClose_Verb == v) {
1857 return true;
1858 }
1859 }
1860 return false;
1861}
1862
1863SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001864 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001866 // A special case: if both points are NaN, SkPoint::operation== returns
1867 // false, but the iterator expects that they are treated as the same.
1868 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001869 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1870 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001871 return kClose_Verb;
1872 }
1873
reed@google.com9e25dbf2012-05-15 17:05:38 +00001874 pts[0] = fLastPt;
1875 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 fLastPt = fMoveTo;
1877 fCloseLine = true;
1878 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001879 } else {
1880 pts[0] = fMoveTo;
1881 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883}
1884
reed@google.com9e25dbf2012-05-15 17:05:38 +00001885const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 if (fSegmentState == kAfterMove_SegmentState) {
1887 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001889 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1892 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001893 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895}
1896
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897void SkPath::Iter::consumeDegenerateSegments() {
1898 // We need to step over anything that will not move the current draw point
1899 // forward before the next move is seen
1900 const uint8_t* lastMoveVerb = 0;
1901 const SkPoint* lastMovePt = 0;
1902 SkPoint lastPt = fLastPt;
1903 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 switch (verb) {
1906 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 // Keep a record of this most recent move
1908 lastMoveVerb = fVerbs;
1909 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001910 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001911 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 fPts++;
1913 break;
1914
1915 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001916 // A close when we are in a segment is always valid except when it
1917 // follows a move which follows a segment.
1918 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 return;
1920 }
1921 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 break;
1924
1925 case kLine_Verb:
1926 if (!IsLineDegenerate(lastPt, fPts[0])) {
1927 if (lastMoveVerb) {
1928 fVerbs = lastMoveVerb;
1929 fPts = lastMovePt;
1930 return;
1931 }
1932 return;
1933 }
1934 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001935 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 fPts++;
1937 break;
1938
reed@google.com277c3f82013-05-31 15:17:50 +00001939 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 case kQuad_Verb:
1941 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1942 if (lastMoveVerb) {
1943 fVerbs = lastMoveVerb;
1944 fPts = lastMovePt;
1945 return;
1946 }
1947 return;
1948 }
1949 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001952 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001953 break;
1954
1955 case kCubic_Verb:
1956 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1957 if (lastMoveVerb) {
1958 fVerbs = lastMoveVerb;
1959 fPts = lastMovePt;
1960 return;
1961 }
1962 return;
1963 }
1964 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001965 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 fPts += 3;
1967 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001968
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001970 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 }
1972 }
1973}
1974
reed@google.com4a3b7142012-05-16 17:16:46 +00001975SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001976 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 // Close the curve if requested and if there is some curve to close
1980 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001981 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 return kLine_Verb;
1983 }
1984 fNeedClose = false;
1985 return kClose_Verb;
1986 }
1987 return kDone_Verb;
1988 }
1989
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001990 // fVerbs is one beyond the current verb, decrement first
1991 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001992 const SkPoint* SK_RESTRICT srcPts = fPts;
1993 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994
1995 switch (verb) {
1996 case kMove_Verb:
1997 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001998 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 verb = this->autoClose(pts);
2000 if (verb == kClose_Verb) {
2001 fNeedClose = false;
2002 }
2003 return (Verb)verb;
2004 }
2005 if (fVerbs == fVerbStop) { // might be a trailing moveto
2006 return kDone_Verb;
2007 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002008 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002009 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002011 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002012 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 fNeedClose = fForceClose;
2014 break;
2015 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002016 pts[0] = this->cons_moveTo();
2017 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 fLastPt = srcPts[0];
2019 fCloseLine = false;
2020 srcPts += 1;
2021 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002022 case kConic_Verb:
2023 fConicWeights += 1;
2024 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002026 pts[0] = this->cons_moveTo();
2027 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 fLastPt = srcPts[1];
2029 srcPts += 2;
2030 break;
2031 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002032 pts[0] = this->cons_moveTo();
2033 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 fLastPt = srcPts[2];
2035 srcPts += 3;
2036 break;
2037 case kClose_Verb:
2038 verb = this->autoClose(pts);
2039 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002040 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 } else {
2042 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002043 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002045 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 break;
2047 }
2048 fPts = srcPts;
2049 return (Verb)verb;
2050}
2051
2052///////////////////////////////////////////////////////////////////////////////
2053
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002054SkPath::RawIter::RawIter() {
2055#ifdef SK_DEBUG
2056 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002057 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002058 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2059#endif
2060 // need to init enough to make next() harmlessly return kDone_Verb
2061 fVerbs = NULL;
2062 fVerbStop = NULL;
2063}
2064
2065SkPath::RawIter::RawIter(const SkPath& path) {
2066 this->setPath(path);
2067}
2068
2069void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002070 fPts = path.fPathRef->points();
2071 fVerbs = path.fPathRef->verbs();
2072 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002073 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002074 fMoveTo.fX = fMoveTo.fY = 0;
2075 fLastPt.fX = fLastPt.fY = 0;
2076}
2077
2078SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002079 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002080 if (fVerbs == fVerbStop) {
2081 return kDone_Verb;
2082 }
2083
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002084 // fVerbs points one beyond next verb so decrement first.
2085 unsigned verb = *(--fVerbs);
2086 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002087
2088 switch (verb) {
2089 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002090 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002091 fMoveTo = srcPts[0];
2092 fLastPt = fMoveTo;
2093 srcPts += 1;
2094 break;
2095 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002096 pts[0] = fLastPt;
2097 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002098 fLastPt = srcPts[0];
2099 srcPts += 1;
2100 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002101 case kConic_Verb:
2102 fConicWeights += 1;
2103 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002104 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002105 pts[0] = fLastPt;
2106 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002107 fLastPt = srcPts[1];
2108 srcPts += 2;
2109 break;
2110 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002111 pts[0] = fLastPt;
2112 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002113 fLastPt = srcPts[2];
2114 srcPts += 3;
2115 break;
2116 case kClose_Verb:
2117 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002118 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002119 break;
2120 }
2121 fPts = srcPts;
2122 return (Verb)verb;
2123}
2124
2125///////////////////////////////////////////////////////////////////////////////
2126
reed@android.com8a1c16f2008-12-17 15:59:43 +00002127/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002128 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129*/
2130
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002131uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 SkDEBUGCODE(this->validate();)
2133
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002134 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002135 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002136 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002137 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002138 return SkAlign4(byteCount);
2139 }
2140
2141 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002142
2143 // Call getBounds() to ensure (as a side-effect) that fBounds
2144 // and fIsFinite are computed.
2145 const SkRect& bounds = this->getBounds();
2146 SkASSERT(!fBoundsIsDirty);
2147
2148 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2149 ((fIsOval & 1) << kIsOval_SerializationShift) |
2150 (fConvexity << kConvexity_SerializationShift) |
2151 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002152 (fSegmentMask << kSegmentMask_SerializationShift) |
2153 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002154
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002155 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002156
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002157 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002158
2159 buffer.write(&bounds, sizeof(bounds));
2160
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002161 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002162 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163}
2164
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002165uint32_t SkPath::readFromMemory(const void* storage) {
2166 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002167
reed@google.com98b11f12011-09-21 18:40:27 +00002168 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002169 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2170 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2171 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2172 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002173 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002174 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002175
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002176 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002177
2178 buffer.read(&fBounds, sizeof(fBounds));
2179 fBoundsIsDirty = false;
2180
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002181 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002182
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002183 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002184
2185 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002186 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187}
2188
2189///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190
reed@google.com51bbe752013-01-17 22:07:50 +00002191#include "SkString.h"
2192
2193static void append_scalar(SkString* str, SkScalar value) {
2194 SkString tmp;
2195 tmp.printf("%g", value);
2196 if (tmp.contains('.')) {
2197 tmp.appendUnichar('f');
2198 }
2199 str->append(tmp);
2200}
2201
2202static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002203 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002204 str->append(label);
2205 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002206
reed@google.com51bbe752013-01-17 22:07:50 +00002207 const SkScalar* values = &pts[0].fX;
2208 count *= 2;
2209
2210 for (int i = 0; i < count; ++i) {
2211 append_scalar(str, values[i]);
2212 if (i < count - 1) {
2213 str->append(", ");
2214 }
2215 }
reed@google.com277c3f82013-05-31 15:17:50 +00002216 if (conicWeight >= 0) {
2217 str->append(", ");
2218 append_scalar(str, conicWeight);
2219 }
reed@google.com51bbe752013-01-17 22:07:50 +00002220 str->append(");\n");
2221}
2222
reed@android.com8a1c16f2008-12-17 15:59:43 +00002223void SkPath::dump(bool forceClose, const char title[]) const {
2224 Iter iter(*this, forceClose);
2225 SkPoint pts[4];
2226 Verb verb;
2227
2228 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2229 title ? title : "");
2230
reed@google.com51bbe752013-01-17 22:07:50 +00002231 SkString builder;
2232
reed@google.com4a3b7142012-05-16 17:16:46 +00002233 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002234 switch (verb) {
2235 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002236 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002237 break;
2238 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002239 append_params(&builder, "path.lineTo", &pts[1], 1);
2240 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002241 break;
2242 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002243 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002244 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002245 case kConic_Verb:
2246 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2247 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002248 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002249 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002250 break;
2251 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002252 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002253 break;
2254 default:
2255 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2256 verb = kDone_Verb; // stop the loop
2257 break;
2258 }
2259 }
reed@google.com51bbe752013-01-17 22:07:50 +00002260 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002261}
2262
reed@android.come522ca52009-11-23 20:10:41 +00002263void SkPath::dump() const {
2264 this->dump(false);
2265}
2266
2267#ifdef SK_DEBUG
2268void SkPath::validate() const {
2269 SkASSERT(this != NULL);
2270 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002271
djsollen@google.com077348c2012-10-22 20:23:32 +00002272#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002273 if (!fBoundsIsDirty) {
2274 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002275
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002276 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002277 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002278
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002279 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002280 // if we're empty, fBounds may be empty but translated, so we can't
2281 // necessarily compare to bounds directly
2282 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2283 // be [2, 2, 2, 2]
2284 SkASSERT(bounds.isEmpty());
2285 SkASSERT(fBounds.isEmpty());
2286 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002287 if (bounds.isEmpty()) {
2288 SkASSERT(fBounds.isEmpty());
2289 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002290 if (!fBounds.isEmpty()) {
2291 SkASSERT(fBounds.contains(bounds));
2292 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002293 }
reed@android.come522ca52009-11-23 20:10:41 +00002294 }
2295 }
reed@google.com10296cc2011-09-21 12:29:05 +00002296
2297 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002298 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2299 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2300 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002301 case kLine_Verb:
2302 mask |= kLine_SegmentMask;
2303 break;
2304 case kQuad_Verb:
2305 mask |= kQuad_SegmentMask;
2306 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002307 case kConic_Verb:
2308 mask |= kConic_SegmentMask;
2309 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002310 case kCubic_Verb:
2311 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002312 case kMove_Verb: // these verbs aren't included in the segment mask.
2313 case kClose_Verb:
2314 break;
2315 case kDone_Verb:
2316 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2317 break;
2318 default:
2319 SkDEBUGFAIL("Unknown Verb");
2320 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002321 }
2322 }
2323 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002324#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002325}
djsollen@google.com077348c2012-10-22 20:23:32 +00002326#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002327
reed@google.com04863fa2011-05-15 04:08:24 +00002328///////////////////////////////////////////////////////////////////////////////
2329
reed@google.com0b7b9822011-05-16 12:29:27 +00002330static int sign(SkScalar x) { return x < 0; }
2331#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002332
reed@google.com04863fa2011-05-15 04:08:24 +00002333static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002334 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002335}
2336
2337// only valid for a single contour
2338struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002339 Convexicator()
2340 : fPtCount(0)
2341 , fConvexity(SkPath::kConvex_Convexity)
2342 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002343 fSign = 0;
2344 // warnings
2345 fCurrPt.set(0, 0);
2346 fVec0.set(0, 0);
2347 fVec1.set(0, 0);
2348 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002349
2350 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002351 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002352 }
2353
2354 SkPath::Convexity getConvexity() const { return fConvexity; }
2355
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 /** The direction returned is only valid if the path is determined convex */
2357 SkPath::Direction getDirection() const { return fDirection; }
2358
reed@google.com04863fa2011-05-15 04:08:24 +00002359 void addPt(const SkPoint& pt) {
2360 if (SkPath::kConcave_Convexity == fConvexity) {
2361 return;
2362 }
2363
2364 if (0 == fPtCount) {
2365 fCurrPt = pt;
2366 ++fPtCount;
2367 } else {
2368 SkVector vec = pt - fCurrPt;
2369 if (vec.fX || vec.fY) {
2370 fCurrPt = pt;
2371 if (++fPtCount == 2) {
2372 fFirstVec = fVec1 = vec;
2373 } else {
2374 SkASSERT(fPtCount > 2);
2375 this->addVec(vec);
2376 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002377
reed@google.com85b6e392011-05-15 20:25:17 +00002378 int sx = sign(vec.fX);
2379 int sy = sign(vec.fY);
2380 fDx += (sx != fSx);
2381 fDy += (sy != fSy);
2382 fSx = sx;
2383 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002384
reed@google.com85b6e392011-05-15 20:25:17 +00002385 if (fDx > 3 || fDy > 3) {
2386 fConvexity = SkPath::kConcave_Convexity;
2387 }
reed@google.com04863fa2011-05-15 04:08:24 +00002388 }
2389 }
2390 }
2391
2392 void close() {
2393 if (fPtCount > 2) {
2394 this->addVec(fFirstVec);
2395 }
2396 }
2397
2398private:
2399 void addVec(const SkVector& vec) {
2400 SkASSERT(vec.fX || vec.fY);
2401 fVec0 = fVec1;
2402 fVec1 = vec;
2403 int sign = CrossProductSign(fVec0, fVec1);
2404 if (0 == fSign) {
2405 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002406 if (1 == sign) {
2407 fDirection = SkPath::kCW_Direction;
2408 } else if (-1 == sign) {
2409 fDirection = SkPath::kCCW_Direction;
2410 }
reed@google.com04863fa2011-05-15 04:08:24 +00002411 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002412 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002413 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002414 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002415 }
2416 }
2417 }
2418
2419 SkPoint fCurrPt;
2420 SkVector fVec0, fVec1, fFirstVec;
2421 int fPtCount; // non-degenerate points
2422 int fSign;
2423 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002424 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002425 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002426};
2427
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428SkPath::Convexity SkPath::internalGetConvexity() const {
2429 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002430 SkPoint pts[4];
2431 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002432 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002433
2434 int contourCount = 0;
2435 int count;
2436 Convexicator state;
2437
2438 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2439 switch (verb) {
2440 case kMove_Verb:
2441 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002442 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002443 return kConcave_Convexity;
2444 }
2445 pts[1] = pts[0];
2446 count = 1;
2447 break;
2448 case kLine_Verb: count = 1; break;
2449 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002450 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002451 case kCubic_Verb: count = 3; break;
2452 case kClose_Verb:
2453 state.close();
2454 count = 0;
2455 break;
2456 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002457 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002458 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002459 return kConcave_Convexity;
2460 }
2461
2462 for (int i = 1; i <= count; i++) {
2463 state.addPt(pts[i]);
2464 }
2465 // early exit
2466 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002467 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002468 return kConcave_Convexity;
2469 }
2470 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002471 fConvexity = state.getConvexity();
2472 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2473 fDirection = state.getDirection();
2474 }
2475 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002476}
reed@google.com69a99432012-01-10 18:00:10 +00002477
2478///////////////////////////////////////////////////////////////////////////////
2479
2480class ContourIter {
2481public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002482 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002483
2484 bool done() const { return fDone; }
2485 // if !done() then these may be called
2486 int count() const { return fCurrPtCount; }
2487 const SkPoint* pts() const { return fCurrPt; }
2488 void next();
2489
2490private:
2491 int fCurrPtCount;
2492 const SkPoint* fCurrPt;
2493 const uint8_t* fCurrVerb;
2494 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002495 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002496 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002497 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002498};
2499
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002500ContourIter::ContourIter(const SkPathRef& pathRef) {
2501 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002502 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002503 fCurrPt = pathRef.points();
2504 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002505 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002506 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002507 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002508 this->next();
2509}
2510
2511void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002513 fDone = true;
2514 }
2515 if (fDone) {
2516 return;
2517 }
2518
2519 // skip pts of prev contour
2520 fCurrPt += fCurrPtCount;
2521
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002522 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002523 int ptCount = 1; // moveTo
2524 const uint8_t* verbs = fCurrVerb;
2525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002526 for (--verbs; verbs > fStopVerbs; --verbs) {
2527 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002528 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002529 goto CONTOUR_END;
2530 case SkPath::kLine_Verb:
2531 ptCount += 1;
2532 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002533 case SkPath::kConic_Verb:
2534 fCurrConicWeight += 1;
2535 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002536 case SkPath::kQuad_Verb:
2537 ptCount += 2;
2538 break;
2539 case SkPath::kCubic_Verb:
2540 ptCount += 3;
2541 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002542 case SkPath::kClose_Verb:
2543 break;
2544 default:
2545 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002546 break;
2547 }
2548 }
2549CONTOUR_END:
2550 fCurrPtCount = ptCount;
2551 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002552 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002553}
2554
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002555// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002556static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002557 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2558 // We may get 0 when the above subtracts underflow. We expect this to be
2559 // very rare and lazily promote to double.
2560 if (0 == cross) {
2561 double p0x = SkScalarToDouble(p0.fX);
2562 double p0y = SkScalarToDouble(p0.fY);
2563
2564 double p1x = SkScalarToDouble(p1.fX);
2565 double p1y = SkScalarToDouble(p1.fY);
2566
2567 double p2x = SkScalarToDouble(p2.fX);
2568 double p2y = SkScalarToDouble(p2.fY);
2569
2570 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2571 (p1y - p0y) * (p2x - p0x));
2572
2573 }
2574 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002575}
2576
reed@google.comc1ea60a2012-01-31 15:15:36 +00002577// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002578static int find_max_y(const SkPoint pts[], int count) {
2579 SkASSERT(count > 0);
2580 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002581 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002582 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002583 SkScalar y = pts[i].fY;
2584 if (y > max) {
2585 max = y;
2586 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002587 }
2588 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002589 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002590}
2591
reed@google.comcabaf1d2012-01-11 21:03:05 +00002592static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2593 int i = index;
2594 for (;;) {
2595 i = (i + inc) % n;
2596 if (i == index) { // we wrapped around, so abort
2597 break;
2598 }
2599 if (pts[index] != pts[i]) { // found a different point, success!
2600 break;
2601 }
2602 }
2603 return i;
2604}
2605
reed@google.comc1ea60a2012-01-31 15:15:36 +00002606/**
2607 * Starting at index, and moving forward (incrementing), find the xmin and
2608 * xmax of the contiguous points that have the same Y.
2609 */
2610static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2611 int* maxIndexPtr) {
2612 const SkScalar y = pts[index].fY;
2613 SkScalar min = pts[index].fX;
2614 SkScalar max = min;
2615 int minIndex = index;
2616 int maxIndex = index;
2617 for (int i = index + 1; i < n; ++i) {
2618 if (pts[i].fY != y) {
2619 break;
2620 }
2621 SkScalar x = pts[i].fX;
2622 if (x < min) {
2623 min = x;
2624 minIndex = i;
2625 } else if (x > max) {
2626 max = x;
2627 maxIndex = i;
2628 }
2629 }
2630 *maxIndexPtr = maxIndex;
2631 return minIndex;
2632}
2633
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002634static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002635 if (dir) {
2636 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2637 }
reed@google.comac8543f2012-01-30 20:51:25 +00002638}
2639
reed@google.comc1ea60a2012-01-31 15:15:36 +00002640#if 0
2641#include "SkString.h"
2642#include "../utils/SkParsePath.h"
2643static void dumpPath(const SkPath& path) {
2644 SkString str;
2645 SkParsePath::ToSVGString(path, &str);
2646 SkDebugf("%s\n", str.c_str());
2647}
2648#endif
2649
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002650namespace {
2651// for use with convex_dir_test
2652double mul(double a, double b) { return a * b; }
2653SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2654double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2655SkScalar toScalar(SkScalar a) { return a; }
2656
2657// determines the winding direction of a convex polygon with the precision
2658// of T. CAST_SCALAR casts an SkScalar to T.
2659template <typename T, T (CAST_SCALAR)(SkScalar)>
2660bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2661 // we find the first three points that form a non-degenerate
2662 // triangle. If there are no such points then the path is
2663 // degenerate. The first is always point 0. Now we find the second
2664 // point.
2665 int i = 0;
2666 enum { kX = 0, kY = 1 };
2667 T v0[2];
2668 while (1) {
2669 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2670 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2671 if (v0[kX] || v0[kY]) {
2672 break;
2673 }
2674 if (++i == n - 1) {
2675 return false;
2676 }
2677 }
2678 // now find a third point that is not colinear with the first two
2679 // points and check the orientation of the triangle (which will be
2680 // the same as the orientation of the path).
2681 for (++i; i < n; ++i) {
2682 T v1[2];
2683 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2684 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2685 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2686 if (0 != cross) {
2687 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2688 return true;
2689 }
2690 }
2691 return false;
2692}
2693}
2694
reed@google.comac8543f2012-01-30 20:51:25 +00002695/*
2696 * We loop through all contours, and keep the computed cross-product of the
2697 * contour that contained the global y-max. If we just look at the first
2698 * contour, we may find one that is wound the opposite way (correctly) since
2699 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2700 * that is outer most (or at least has the global y-max) before we can consider
2701 * its cross product.
2702 */
reed@google.com69a99432012-01-10 18:00:10 +00002703bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002704// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002705 // don't want to pay the cost for computing this if it
2706 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002707
2708 if (kUnknown_Direction != fDirection) {
2709 *dir = static_cast<Direction>(fDirection);
2710 return true;
2711 }
reed@google.com69a99432012-01-10 18:00:10 +00002712 const Convexity conv = this->getConvexityOrUnknown();
2713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002714 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002715
reed@google.comac8543f2012-01-30 20:51:25 +00002716 // initialize with our logical y-min
2717 SkScalar ymax = this->getBounds().fTop;
2718 SkScalar ymaxCross = 0;
2719
reed@google.com69a99432012-01-10 18:00:10 +00002720 for (; !iter.done(); iter.next()) {
2721 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002722 if (n < 3) {
2723 continue;
2724 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002725
reed@google.comcabaf1d2012-01-11 21:03:05 +00002726 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002727 SkScalar cross = 0;
2728 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002729 // We try first at scalar precision, and then again at double
2730 // precision. This is because the vectors computed between distant
2731 // points may lose too much precision.
2732 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002733 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002734 return true;
2735 }
2736 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002737 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002738 return true;
2739 } else {
2740 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002741 }
2742 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002743 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002744 if (pts[index].fY < ymax) {
2745 continue;
2746 }
2747
reed@google.comc1ea60a2012-01-31 15:15:36 +00002748 // If there is more than 1 distinct point at the y-max, we take the
2749 // x-min and x-max of them and just subtract to compute the dir.
2750 if (pts[(index + 1) % n].fY == pts[index].fY) {
2751 int maxIndex;
2752 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002753 if (minIndex == maxIndex) {
2754 goto TRY_CROSSPROD;
2755 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002756 SkASSERT(pts[minIndex].fY == pts[index].fY);
2757 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2758 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2759 // we just subtract the indices, and let that auto-convert to
2760 // SkScalar, since we just want - or + to signal the direction.
2761 cross = minIndex - maxIndex;
2762 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002763 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002764 // Find a next and prev index to use for the cross-product test,
2765 // but we try to find pts that form non-zero vectors from pts[index]
2766 //
2767 // Its possible that we can't find two non-degenerate vectors, so
2768 // we have to guard our search (e.g. all the pts could be in the
2769 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002770
reed@google.comc1ea60a2012-01-31 15:15:36 +00002771 // we pass n - 1 instead of -1 so we don't foul up % operator by
2772 // passing it a negative LH argument.
2773 int prev = find_diff_pt(pts, index, n, n - 1);
2774 if (prev == index) {
2775 // completely degenerate, skip to next contour
2776 continue;
2777 }
2778 int next = find_diff_pt(pts, index, n, 1);
2779 SkASSERT(next != index);
2780 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002781 // 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 +00002782 // x-direction. We really should continue to walk away from the degeneracy until
2783 // there is a divergence.
2784 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002785 // construct the subtract so we get the correct Direction below
2786 cross = pts[index].fX - pts[next].fX;
2787 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002788 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002789
reed@google.comac8543f2012-01-30 20:51:25 +00002790 if (cross) {
2791 // record our best guess so far
2792 ymax = pts[index].fY;
2793 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002794 }
reed@google.com69a99432012-01-10 18:00:10 +00002795 }
2796 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002797 if (ymaxCross) {
2798 crossToDir(ymaxCross, dir);
2799 fDirection = *dir;
2800 return true;
2801 } else {
2802 return false;
2803 }
reed@google.comac8543f2012-01-30 20:51:25 +00002804}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805
2806///////////////////////////////////////////////////////////////////////////////
2807
2808static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2809 SkScalar D, SkScalar t) {
2810 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2811}
2812
2813static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2814 SkScalar t) {
2815 SkScalar A = c3 + 3*(c1 - c2) - c0;
2816 SkScalar B = 3*(c2 - c1 - c1 + c0);
2817 SkScalar C = 3*(c1 - c0);
2818 SkScalar D = c0;
2819 return eval_cubic_coeff(A, B, C, D, t);
2820}
2821
2822/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2823 t value such that cubic(t) = target
2824 */
2825static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2826 SkScalar target, SkScalar* t) {
2827 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2828 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002829
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 SkScalar D = c0 - target;
2831 SkScalar A = c3 + 3*(c1 - c2) - c0;
2832 SkScalar B = 3*(c2 - c1 - c1 + c0);
2833 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002834
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2836 SkScalar minT = 0;
2837 SkScalar maxT = SK_Scalar1;
2838 SkScalar mid;
2839 int i;
2840 for (i = 0; i < 16; i++) {
2841 mid = SkScalarAve(minT, maxT);
2842 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2843 if (delta < 0) {
2844 minT = mid;
2845 delta = -delta;
2846 } else {
2847 maxT = mid;
2848 }
2849 if (delta < TOLERANCE) {
2850 break;
2851 }
2852 }
2853 *t = mid;
2854 return true;
2855}
2856
2857template <size_t N> static void find_minmax(const SkPoint pts[],
2858 SkScalar* minPtr, SkScalar* maxPtr) {
2859 SkScalar min, max;
2860 min = max = pts[0].fX;
2861 for (size_t i = 1; i < N; ++i) {
2862 min = SkMinScalar(min, pts[i].fX);
2863 max = SkMaxScalar(max, pts[i].fX);
2864 }
2865 *minPtr = min;
2866 *maxPtr = max;
2867}
2868
2869static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2870 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002871
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 int dir = 1;
2873 if (pts[0].fY > pts[3].fY) {
2874 storage[0] = pts[3];
2875 storage[1] = pts[2];
2876 storage[2] = pts[1];
2877 storage[3] = pts[0];
2878 pts = storage;
2879 dir = -1;
2880 }
2881 if (y < pts[0].fY || y >= pts[3].fY) {
2882 return 0;
2883 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 // quickreject or quickaccept
2886 SkScalar min, max;
2887 find_minmax<4>(pts, &min, &max);
2888 if (x < min) {
2889 return 0;
2890 }
2891 if (x > max) {
2892 return dir;
2893 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 // compute the actual x(t) value
2896 SkScalar t, xt;
2897 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2898 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2899 } else {
2900 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2901 xt = y < mid ? pts[0].fX : pts[3].fX;
2902 }
2903 return xt < x ? dir : 0;
2904}
2905
2906static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2907 SkPoint dst[10];
2908 int n = SkChopCubicAtYExtrema(pts, dst);
2909 int w = 0;
2910 for (int i = 0; i <= n; ++i) {
2911 w += winding_mono_cubic(&dst[i * 3], x, y);
2912 }
2913 return w;
2914}
2915
2916static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2917 SkScalar y0 = pts[0].fY;
2918 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002919
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 int dir = 1;
2921 if (y0 > y2) {
2922 SkTSwap(y0, y2);
2923 dir = -1;
2924 }
2925 if (y < y0 || y >= y2) {
2926 return 0;
2927 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002928
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 // bounds check on X (not required. is it faster?)
2930#if 0
2931 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2932 return 0;
2933 }
2934#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002935
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 SkScalar roots[2];
2937 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2938 2 * (pts[1].fY - pts[0].fY),
2939 pts[0].fY - y,
2940 roots);
2941 SkASSERT(n <= 1);
2942 SkScalar xt;
2943 if (0 == n) {
2944 SkScalar mid = SkScalarAve(y0, y2);
2945 // Need [0] and [2] if dir == 1
2946 // and [2] and [0] if dir == -1
2947 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2948 } else {
2949 SkScalar t = roots[0];
2950 SkScalar C = pts[0].fX;
2951 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2952 SkScalar B = 2 * (pts[1].fX - C);
2953 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2954 }
2955 return xt < x ? dir : 0;
2956}
2957
2958static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2959 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2960 if (y0 == y1) {
2961 return true;
2962 }
2963 if (y0 < y1) {
2964 return y1 <= y2;
2965 } else {
2966 return y1 >= y2;
2967 }
2968}
2969
2970static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2971 SkPoint dst[5];
2972 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002973
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2975 n = SkChopQuadAtYExtrema(pts, dst);
2976 pts = dst;
2977 }
2978 int w = winding_mono_quad(pts, x, y);
2979 if (n > 0) {
2980 w += winding_mono_quad(&pts[2], x, y);
2981 }
2982 return w;
2983}
2984
2985static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2986 SkScalar x0 = pts[0].fX;
2987 SkScalar y0 = pts[0].fY;
2988 SkScalar x1 = pts[1].fX;
2989 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002990
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002992
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 int dir = 1;
2994 if (y0 > y1) {
2995 SkTSwap(y0, y1);
2996 dir = -1;
2997 }
2998 if (y < y0 || y >= y1) {
2999 return 0;
3000 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003001
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
3003 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 if (SkScalarSignAsInt(cross) == dir) {
3006 dir = 0;
3007 }
3008 return dir;
3009}
3010
3011bool SkPath::contains(SkScalar x, SkScalar y) const {
3012 bool isInverse = this->isInverseFillType();
3013 if (this->isEmpty()) {
3014 return isInverse;
3015 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003016
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003017 const SkRect& bounds = this->getBounds();
3018 if (!bounds.contains(x, y)) {
3019 return isInverse;
3020 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003021
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 SkPath::Iter iter(*this, true);
3023 bool done = false;
3024 int w = 0;
3025 do {
3026 SkPoint pts[4];
3027 switch (iter.next(pts, false)) {
3028 case SkPath::kMove_Verb:
3029 case SkPath::kClose_Verb:
3030 break;
3031 case SkPath::kLine_Verb:
3032 w += winding_line(pts, x, y);
3033 break;
3034 case SkPath::kQuad_Verb:
3035 w += winding_quad(pts, x, y);
3036 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003037 case SkPath::kConic_Verb:
3038 SkASSERT(0);
3039 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003040 case SkPath::kCubic_Verb:
3041 w += winding_cubic(pts, x, y);
3042 break;
3043 case SkPath::kDone_Verb:
3044 done = true;
3045 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003046 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003047 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003048
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003049 switch (this->getFillType()) {
3050 case SkPath::kEvenOdd_FillType:
3051 case SkPath::kInverseEvenOdd_FillType:
3052 w &= 1;
3053 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003054 default:
3055 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003056 }
3057 return SkToBool(w);
3058}