blob: 307f7fddd058a3fe174cb18851c12a398848f071 [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
10#include "SkPath.h"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
14#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000016
17////////////////////////////////////////////////////////////////////////////
18
19#if SK_DEBUG_PATH_REF
20
21SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
22
23SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
24: fPathRef(pr)
25, fOwner(owner) {
26 pr->addOwner(owner);
27}
28
29SkPath::PathRefDebugRef::~PathRefDebugRef() {
30 fPathRef->removeOwner(fOwner);
31}
32
33void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
34 bool diff = (ref != fPathRef.get());
35 if (diff && NULL != fPathRef.get()) {
36 fPathRef.get()->removeOwner(fOwner);
37 }
38 fPathRef.reset(ref);
39 if (diff && NULL != fPathRef.get()) {
40 fPathRef.get()->addOwner(fOwner);
41 }
42}
43
44void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
45 if (other->fPathRef.get() != fPathRef.get()) {
46 other->fPathRef->removeOwner(other->fOwner);
47 other->fPathRef->addOwner(fOwner);
48
49 fPathRef->removeOwner(fOwner);
50 fPathRef->addOwner(other->fOwner);
51 }
52
53 fPathRef.swap(&other->fPathRef);
54}
55
56SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
57
58SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
59 return fPathRef.operator->();
60}
61
62SkPath::PathRefDebugRef::operator SkPathRef*() {
63 return fPathRef.operator SkPathRef *();
64}
65
66#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000067
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000068////////////////////////////////////////////////////////////////////////////
69
70
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000071SK_DEFINE_INST_COUNT(SkPath);
72
reed@google.com744faba2012-05-29 19:54:52 +000073// This value is just made-up for now. When count is 4, calling memset was much
74// slower than just writing the loop. This seems odd, and hopefully in the
75// future this we appear to have been a fluke...
76#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
77
reed@android.com8a1c16f2008-12-17 15:59:43 +000078////////////////////////////////////////////////////////////////////////////
79
reed@google.com3563c9e2011-11-14 19:34:57 +000080/**
81 * Path.bounds is defined to be the bounds of all the control points.
82 * If we called bounds.join(r) we would skip r if r was empty, which breaks
83 * our promise. Hence we have a custom joiner that doesn't look at emptiness
84 */
85static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
86 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
87 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
88 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
89 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
90}
91
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000092static bool is_degenerate(const SkPath& path) {
93 SkPath::Iter iter(path, false);
94 SkPoint pts[4];
95 return SkPath::kDone_Verb == iter.next(pts);
96}
97
bsalomon@google.com6aa29652012-04-18 13:29:52 +000098class SkAutoDisableOvalCheck {
99public:
100 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
101 fSaved = fPath->fIsOval;
102 }
103
104 ~SkAutoDisableOvalCheck() {
105 fPath->fIsOval = fSaved;
106 }
107
108private:
109 SkPath* fPath;
110 bool fSaved;
111};
112
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000113class SkAutoDisableDirectionCheck {
114public:
115 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
116 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
117 }
118
119 ~SkAutoDisableDirectionCheck() {
120 fPath->fDirection = fSaved;
121 }
122
123private:
124 SkPath* fPath;
125 SkPath::Direction fSaved;
126};
127
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128/* This guy's constructor/destructor bracket a path editing operation. It is
129 used when we know the bounds of the amount we are going to add to the path
130 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 It captures some state about the path up front (i.e. if it already has a
133 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000134 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000135
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000136 It also notes if the path was originally degenerate, and if so, sets
137 isConvex to true. Thus it can only be used if the contour being added is
138 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 */
140class SkAutoPathBoundsUpdate {
141public:
142 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
143 this->init(path);
144 }
145
146 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
147 SkScalar right, SkScalar bottom) {
148 fRect.set(left, top, right, bottom);
149 this->init(path);
150 }
reed@google.comabf15c12011-01-18 20:35:51 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000153 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000155 fPath->fBounds = fRect;
156 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000157 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000159 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000160 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000161 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 }
163 }
reed@google.comabf15c12011-01-18 20:35:51 +0000164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000166 SkPath* fPath;
167 SkRect fRect;
168 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000169 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000170 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000173 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000175 // Mark the path's bounds as dirty if (1) they are, or (2) the path
176 // is non-finite, and therefore its bounds are not meaningful
177 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000178 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000180 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000181 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 }
183};
184
reed@google.com0bb18bb2012-07-26 15:20:36 +0000185// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000186static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
187 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000188 if (count <= 1) { // we ignore just 1 point (moveto)
189 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000190 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000192 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
196////////////////////////////////////////////////////////////////////////////
197
198/*
199 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000200 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
202
203 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000204 1. If we encounter degenerate segments, remove them
205 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
206 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
207 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208*/
209
210////////////////////////////////////////////////////////////////////////////
211
reed@google.comd335d1d2012-01-12 18:17:11 +0000212// flag to require a moveTo if we begin with something else, like lineTo etc.
213#define INITIAL_LASTMOVETOINDEX_VALUE ~0
214
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000215SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000216#if SK_DEBUG_PATH_REF
217 : fPathRef(SkPathRef::CreateEmpty(), this)
218#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000219 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000220#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000221 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000222 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000223 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000224 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000225 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000226 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000227 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000228 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000229#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000230 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000231 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000232#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000233}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000235SkPath::SkPath(const SkPath& src)
236#if SK_DEBUG_PATH_REF
237 : fPathRef(this)
238#endif
239{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000241 src.fPathRef.get()->ref();
242 fPathRef.reset(src.fPathRef.get());
243 fBounds = src.fBounds;
244 fFillType = src.fFillType;
245 fBoundsIsDirty = src.fBoundsIsDirty;
246 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000247 fDirection = src.fDirection;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000248 fIsFinite = src.fIsFinite;
249 fSegmentMask = src.fSegmentMask;
250 fLastMoveToIndex = src.fLastMoveToIndex;
251 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000252#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000253 fGenerationID = src.fGenerationID;
254 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000255#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256}
257
258SkPath::~SkPath() {
259 SkDEBUGCODE(this->validate();)
260}
261
262SkPath& SkPath::operator=(const SkPath& src) {
263 SkDEBUGCODE(src.validate();)
264
265 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000266 src.fPathRef.get()->ref();
267 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000268 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000269 fFillType = src.fFillType;
270 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000271 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000272 fDirection = src.fDirection;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000273 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000274 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000275 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000276 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000277 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 }
279 SkDEBUGCODE(this->validate();)
280 return *this;
281}
282
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000283SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000284 // note: don't need to look at isConvex or bounds, since just comparing the
285 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000286
287 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
288 // since it is only a cache of info in the fVerbs, but its a fast way to
289 // notice a difference
290
reed@android.com3abec1d2009-03-02 05:36:20 +0000291 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000292 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000293 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000294}
295
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296void SkPath::swap(SkPath& other) {
297 SkASSERT(&other != NULL);
298
299 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000300 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000301 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000303 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000304 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000305 SkTSwap<uint8_t>(fDirection, other.fDirection);
reed@google.com10296cc2011-09-21 12:29:05 +0000306 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000307 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000308 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000309 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000310 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 }
312}
313
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314static inline bool check_edge_against_rect(const SkPoint& p0,
315 const SkPoint& p1,
316 const SkRect& rect,
317 SkPath::Direction dir) {
318 const SkPoint* edgeBegin;
319 SkVector v;
320 if (SkPath::kCW_Direction == dir) {
321 v = p1 - p0;
322 edgeBegin = &p0;
323 } else {
324 v = p0 - p1;
325 edgeBegin = &p1;
326 }
327 if (v.fX || v.fY) {
328 // check the cross product of v with the vec from edgeBegin to each rect corner
329 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
330 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
331 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
332 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
333 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
334 return false;
335 }
336 }
337 return true;
338}
339
340bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
341 // This only handles non-degenerate convex paths currently.
342 if (kConvex_Convexity != this->getConvexity()) {
343 return false;
344 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000345
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000346 Direction direction;
347 if (!this->cheapComputeDirection(&direction)) {
348 return false;
349 }
350
351 SkPoint firstPt;
352 SkPoint prevPt;
353 RawIter iter(*this);
354 SkPath::Verb verb;
355 SkPoint pts[4];
356 SkDEBUGCODE(int moveCnt = 0;)
357
358 while ((verb = iter.next(pts)) != kDone_Verb) {
359 int nextPt = -1;
360 switch (verb) {
361 case kMove_Verb:
362 SkASSERT(!moveCnt);
363 SkDEBUGCODE(++moveCnt);
364 firstPt = prevPt = pts[0];
365 break;
366 case kLine_Verb:
367 nextPt = 1;
368 SkASSERT(moveCnt);
369 break;
370 case kQuad_Verb:
371 SkASSERT(moveCnt);
372 nextPt = 2;
373 break;
374 case kCubic_Verb:
375 SkASSERT(moveCnt);
376 nextPt = 3;
377 break;
378 case kClose_Verb:
379 break;
380 default:
381 SkDEBUGFAIL("unknown verb");
382 }
383 if (-1 != nextPt) {
384 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
385 return false;
386 }
387 prevPt = pts[nextPt];
388 }
389 }
390
391 return check_edge_against_rect(prevPt, firstPt, rect, direction);
392}
393
djsollen@google.com56c69772011-11-08 19:00:26 +0000394#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000395uint32_t SkPath::getGenerationID() const {
396 return fGenerationID;
397}
djsollen@google.come63793a2012-03-21 15:39:03 +0000398
399const SkPath* SkPath::getSourcePath() const {
400 return fSourcePath;
401}
402
403void SkPath::setSourcePath(const SkPath* path) {
404 fSourcePath = path;
405}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000406#endif
407
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408void SkPath::reset() {
409 SkDEBUGCODE(this->validate();)
410
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000411 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000412 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000413 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000414 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000415 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000416 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000417 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000418 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419}
420
421void SkPath::rewind() {
422 SkDEBUGCODE(this->validate();)
423
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000424 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000425 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000426 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000427 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000428 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000429 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000430 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431}
432
433bool SkPath::isEmpty() const {
434 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000435 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436}
437
reed@google.com7e6c4d12012-05-10 14:05:43 +0000438bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000439 int verbCount = fPathRef->countVerbs();
440 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000441
reed@google.com7e6c4d12012-05-10 14:05:43 +0000442 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000443 if (kMove_Verb == fPathRef->atVerb(0) &&
444 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000445 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000446 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000447 line[0] = pts[0];
448 line[1] = pts[1];
449 }
450 return true;
451 }
452 }
453 return false;
454}
455
caryclark@google.comf1316942011-07-26 19:54:45 +0000456/*
457 Determines if path is a rect by keeping track of changes in direction
458 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000459
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 The direction is computed such that:
461 0: vertical up
462 1: horizontal right
463 2: vertical down
464 3: horizontal left
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000465
caryclark@google.comf1316942011-07-26 19:54:45 +0000466A rectangle cycles up/right/down/left or up/left/down/right.
467
468The test fails if:
469 The path is closed, and followed by a line.
470 A second move creates a new endpoint.
471 A diagonal line is parsed.
472 There's more than four changes of direction.
473 There's a discontinuity on the line (e.g., a move in the middle)
474 The line reverses direction.
475 The rectangle doesn't complete a cycle.
476 The path contains a quadratic or cubic.
477 The path contains fewer than four points.
478 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000479
caryclark@google.comf1316942011-07-26 19:54:45 +0000480It's OK if the path has:
481 Several colinear line segments composing a rectangle side.
482 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000483
caryclark@google.comf1316942011-07-26 19:54:45 +0000484The direction takes advantage of the corners found since opposite sides
485must travel in opposite directions.
486
487FIXME: Allow colinear quads and cubics to be treated like lines.
488FIXME: If the API passes fill-only, return true if the filled stroke
489 is a rectangle, though the caller failed to close the path.
490 */
caryclark@google.com56f233a2012-11-19 13:06:06 +0000491bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr) 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:
559 case kCubic_Verb:
560 return false; // quadratic, cubic not allowed
561 case kMove_Verb:
562 last = *pts++;
563 closedOrMoved = true;
564 break;
565 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000566 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000567 lastDirection = nextDirection;
568 }
569 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000570 bool result = 4 == corners && (first == last || autoClose);
571 if (savePts) {
572 *ptsPtr = savePts;
573 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000574 return result;
575}
576
577bool SkPath::isRect(SkRect* rect) const {
578 SkDEBUGCODE(this->validate();)
579 int currVerb = 0;
580 const SkPoint* pts = fPathRef->points();
581 bool result = isRectContour(false, &currVerb, &pts);
caryclark@google.comf1316942011-07-26 19:54:45 +0000582 if (result && rect) {
583 *rect = getBounds();
584 }
585 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586}
587
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588bool SkPath::isNestedRects(SkRect rects[2]) const {
589 SkDEBUGCODE(this->validate();)
590 int currVerb = 0;
591 const SkPoint* pts = fPathRef->points();
592 const SkPoint* first = pts;
593 if (!isRectContour(true, &currVerb, &pts)) {
594 return false;
595 }
596 const SkPoint* last = pts;
597 SkRect testRects[2];
598 if (isRectContour(false, &currVerb, &pts)) {
599 testRects[0].set(first, last - first);
600 testRects[1].set(last, pts - last);
601 if (testRects[0].contains(testRects[1])) {
602 if (rects) {
603 rects[0] = testRects[0];
604 rects[1] = testRects[1];
605 }
606 return true;
607 }
608 if (testRects[1].contains(testRects[0])) {
609 if (rects) {
610 rects[0] = testRects[1];
611 rects[1] = testRects[0];
612 }
613 return true;
614 }
615 }
616 return false;
617}
618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000619int SkPath::countPoints() const {
620 return fPathRef->countPoints();
621}
622
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000623int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 SkDEBUGCODE(this->validate();)
625
626 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000627 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000628 int count = SkMin32(max, fPathRef->countPoints());
629 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
630 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000631}
632
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000633SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
635 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000636 }
637 return SkPoint::Make(0, 0);
638}
639
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640int SkPath::countVerbs() const {
641 return fPathRef->countVerbs();
642}
643
644static inline void copy_verbs_reverse(uint8_t* inorderDst,
645 const uint8_t* reversedSrc,
646 int count) {
647 for (int i = 0; i < count; ++i) {
648 inorderDst[i] = reversedSrc[~i];
649 }
650}
651
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000652int SkPath::getVerbs(uint8_t dst[], int max) const {
653 SkDEBUGCODE(this->validate();)
654
655 SkASSERT(max >= 0);
656 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 int count = SkMin32(max, fPathRef->countVerbs());
658 copy_verbs_reverse(dst, fPathRef->verbs(), count);
659 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000660}
661
reed@google.com294dd7b2011-10-11 11:58:32 +0000662bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 SkDEBUGCODE(this->validate();)
664
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000666 if (count > 0) {
667 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000670 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000672 if (lastPt) {
673 lastPt->set(0, 0);
674 }
675 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676}
677
678void SkPath::setLastPt(SkScalar x, SkScalar y) {
679 SkDEBUGCODE(this->validate();)
680
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 if (count == 0) {
683 this->moveTo(x, y);
684 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000685 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 SkPathRef::Editor ed(&fPathRef);
687 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000688 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 }
690}
691
reed@android.comd252db02009-04-01 18:31:44 +0000692void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000694 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000696 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000697 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698}
699
reed@google.com04863fa2011-05-15 04:08:24 +0000700void SkPath::setConvexity(Convexity c) {
701 if (fConvexity != c) {
702 fConvexity = c;
703 GEN_ID_INC;
704 }
705}
706
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707//////////////////////////////////////////////////////////////////////////////
708// Construction methods
709
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000710#define DIRTY_AFTER_EDIT \
711 do { \
712 fBoundsIsDirty = true; \
713 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000714 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000715 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000716 } while (0)
717
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000718#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
719 do { \
720 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000721 } while (0)
722
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723void SkPath::incReserve(U16CPU inc) {
724 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 SkDEBUGCODE(this->validate();)
727}
728
729void SkPath::moveTo(SkScalar x, SkScalar y) {
730 SkDEBUGCODE(this->validate();)
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733
reed@google.comd335d1d2012-01-12 18:17:11 +0000734 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000736
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000737 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000739 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000740 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741}
742
743void SkPath::rMoveTo(SkScalar x, SkScalar y) {
744 SkPoint pt;
745 this->getLastPt(&pt);
746 this->moveTo(pt.fX + x, pt.fY + y);
747}
748
reed@google.comd335d1d2012-01-12 18:17:11 +0000749void SkPath::injectMoveToIfNeeded() {
750 if (fLastMoveToIndex < 0) {
751 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000752 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000753 x = y = 0;
754 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000756 x = pt.fX;
757 y = pt.fY;
758 }
759 this->moveTo(x, y);
760 }
761}
762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763void SkPath::lineTo(SkScalar x, SkScalar y) {
764 SkDEBUGCODE(this->validate();)
765
reed@google.comd335d1d2012-01-12 18:17:11 +0000766 this->injectMoveToIfNeeded();
767
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000768 SkPathRef::Editor ed(&fPathRef);
769 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000770 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000772 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000773 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774}
775
776void SkPath::rLineTo(SkScalar x, SkScalar y) {
777 SkPoint pt;
778 this->getLastPt(&pt);
779 this->lineTo(pt.fX + x, pt.fY + y);
780}
781
782void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
783 SkDEBUGCODE(this->validate();)
784
reed@google.comd335d1d2012-01-12 18:17:11 +0000785 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000787 SkPathRef::Editor ed(&fPathRef);
788 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 pts[0].set(x1, y1);
790 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000791 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000793 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000794 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795}
796
797void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
798 SkPoint pt;
799 this->getLastPt(&pt);
800 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
801}
802
803void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
804 SkScalar x3, SkScalar y3) {
805 SkDEBUGCODE(this->validate();)
806
reed@google.comd335d1d2012-01-12 18:17:11 +0000807 this->injectMoveToIfNeeded();
808
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000809 SkPathRef::Editor ed(&fPathRef);
810 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 pts[0].set(x1, y1);
812 pts[1].set(x2, y2);
813 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000814 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +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::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
821 SkScalar x3, SkScalar y3) {
822 SkPoint pt;
823 this->getLastPt(&pt);
824 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
825 pt.fX + x3, pt.fY + y3);
826}
827
828void SkPath::close() {
829 SkDEBUGCODE(this->validate();)
830
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000831 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000833 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 case kLine_Verb:
835 case kQuad_Verb:
836 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000837 case kMove_Verb: {
838 SkPathRef::Editor ed(&fPathRef);
839 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000840 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000842 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000844 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 break;
846 }
847 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000848
849 // signal that we need a moveTo to follow us (unless we're done)
850#if 0
851 if (fLastMoveToIndex >= 0) {
852 fLastMoveToIndex = ~fLastMoveToIndex;
853 }
854#else
855 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
856#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857}
858
859///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000860
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861void SkPath::addRect(const SkRect& rect, Direction dir) {
862 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
863}
864
865void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
866 SkScalar bottom, Direction dir) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000867 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
868 SkAutoDisableDirectionCheck addc(this);
869
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
871
872 this->incReserve(5);
873
874 this->moveTo(left, top);
875 if (dir == kCCW_Direction) {
876 this->lineTo(left, bottom);
877 this->lineTo(right, bottom);
878 this->lineTo(right, top);
879 } else {
880 this->lineTo(right, top);
881 this->lineTo(right, bottom);
882 this->lineTo(left, bottom);
883 }
884 this->close();
885}
886
reed@google.com744faba2012-05-29 19:54:52 +0000887void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
888 SkDEBUGCODE(this->validate();)
889 if (count <= 0) {
890 return;
891 }
892
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000893 SkPathRef::Editor ed(&fPathRef);
894 fLastMoveToIndex = ed.pathRef()->countPoints();
895 uint8_t* vb;
896 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000897 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000898 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000899
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000900 memcpy(p, pts, count * sizeof(SkPoint));
901 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000902 if (count > 1) {
903 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
904 // be 0, the compiler will remove the test/branch entirely.
905 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000906 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000907 } else {
908 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000909 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000910 }
911 }
912 fSegmentMask |= kLine_SegmentMask;
913 }
914 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000915 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000916 }
917
918 GEN_ID_INC;
919 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000920 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000921}
922
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
924
925void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
926 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927 SkScalar w = rect.width();
928 SkScalar halfW = SkScalarHalf(w);
929 SkScalar h = rect.height();
930 SkScalar halfH = SkScalarHalf(h);
931
932 if (halfW <= 0 || halfH <= 0) {
933 return;
934 }
935
reed@google.comabf15c12011-01-18 20:35:51 +0000936 bool skip_hori = rx >= halfW;
937 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938
939 if (skip_hori && skip_vert) {
940 this->addOval(rect, dir);
941 return;
942 }
reed@google.comabf15c12011-01-18 20:35:51 +0000943
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000944 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
945
reed@google.comabf15c12011-01-18 20:35:51 +0000946 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000947 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000948
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949 if (skip_hori) {
950 rx = halfW;
951 } else if (skip_vert) {
952 ry = halfH;
953 }
954
955 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
956 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
957
958 this->incReserve(17);
959 this->moveTo(rect.fRight - rx, rect.fTop);
960 if (dir == kCCW_Direction) {
961 if (!skip_hori) {
962 this->lineTo(rect.fLeft + rx, rect.fTop); // top
963 }
964 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
965 rect.fLeft, rect.fTop + ry - sy,
966 rect.fLeft, rect.fTop + ry); // top-left
967 if (!skip_vert) {
968 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
969 }
970 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
971 rect.fLeft + rx - sx, rect.fBottom,
972 rect.fLeft + rx, rect.fBottom); // bot-left
973 if (!skip_hori) {
974 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
975 }
976 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
977 rect.fRight, rect.fBottom - ry + sy,
978 rect.fRight, rect.fBottom - ry); // bot-right
979 if (!skip_vert) {
980 this->lineTo(rect.fRight, rect.fTop + ry);
981 }
982 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
983 rect.fRight - rx + sx, rect.fTop,
984 rect.fRight - rx, rect.fTop); // top-right
985 } else {
986 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
987 rect.fRight, rect.fTop + ry - sy,
988 rect.fRight, rect.fTop + ry); // top-right
989 if (!skip_vert) {
990 this->lineTo(rect.fRight, rect.fBottom - ry);
991 }
992 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
993 rect.fRight - rx + sx, rect.fBottom,
994 rect.fRight - rx, rect.fBottom); // bot-right
995 if (!skip_hori) {
996 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
997 }
998 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
999 rect.fLeft, rect.fBottom - ry + sy,
1000 rect.fLeft, rect.fBottom - ry); // bot-left
1001 if (!skip_vert) {
1002 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1003 }
1004 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1005 rect.fLeft + rx - sx, rect.fTop,
1006 rect.fLeft + rx, rect.fTop); // top-left
1007 if (!skip_hori) {
1008 this->lineTo(rect.fRight - rx, rect.fTop); // top
1009 }
1010 }
1011 this->close();
1012}
1013
1014static void add_corner_arc(SkPath* path, const SkRect& rect,
1015 SkScalar rx, SkScalar ry, int startAngle,
1016 SkPath::Direction dir, bool forceMoveTo) {
1017 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
1018 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +00001019
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 SkRect r;
1021 r.set(-rx, -ry, rx, ry);
1022
1023 switch (startAngle) {
1024 case 0:
1025 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1026 break;
1027 case 90:
1028 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1029 break;
1030 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1031 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001032 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033 }
reed@google.comabf15c12011-01-18 20:35:51 +00001034
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 SkScalar start = SkIntToScalar(startAngle);
1036 SkScalar sweep = SkIntToScalar(90);
1037 if (SkPath::kCCW_Direction == dir) {
1038 start += sweep;
1039 sweep = -sweep;
1040 }
reed@google.comabf15c12011-01-18 20:35:51 +00001041
reed@android.com8a1c16f2008-12-17 15:59:43 +00001042 path->arcTo(r, start, sweep, forceMoveTo);
1043}
1044
1045void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
1046 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +00001047 // abort before we invoke SkAutoPathBoundsUpdate()
1048 if (rect.isEmpty()) {
1049 return;
1050 }
1051
reed@android.com8a1c16f2008-12-17 15:59:43 +00001052 SkAutoPathBoundsUpdate apbu(this, rect);
1053
1054 if (kCW_Direction == dir) {
1055 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1056 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1057 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1058 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1059 } else {
1060 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1061 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1062 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1063 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1064 }
1065 this->close();
1066}
1067
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001068bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001069 int count = fPathRef->countVerbs();
1070 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1071 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001072 if (*verbs == kLine_Verb ||
1073 *verbs == kQuad_Verb ||
1074 *verbs == kCubic_Verb) {
1075 return false;
1076 }
1077 ++verbs;
1078 }
1079 return true;
1080}
1081
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001083 /* If addOval() is called after previous moveTo(),
1084 this path is still marked as an oval. This is used to
1085 fit into WebKit's calling sequences.
1086 We can't simply check isEmpty() in this case, as additional
1087 moveTo() would mark the path non empty.
1088 */
1089 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001090 if (fIsOval) {
1091 fDirection = dir;
1092 } else {
1093 fDirection = kUnknown_Direction;
1094 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001095
1096 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001097 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001098
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 SkAutoPathBoundsUpdate apbu(this, oval);
1100
1101 SkScalar cx = oval.centerX();
1102 SkScalar cy = oval.centerY();
1103 SkScalar rx = SkScalarHalf(oval.width());
1104 SkScalar ry = SkScalarHalf(oval.height());
1105#if 0 // these seem faster than using quads (1/2 the number of edges)
1106 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1107 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1108
1109 this->incReserve(13);
1110 this->moveTo(cx + rx, cy);
1111 if (dir == kCCW_Direction) {
1112 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1113 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1114 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1115 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1116 } else {
1117 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
1118 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
1119 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
1120 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
1121 }
1122#else
1123 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1124 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1125 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1126 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1127
1128 /*
1129 To handle imprecision in computing the center and radii, we revert to
1130 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1131 to ensure that we don't exceed the oval's bounds *ever*, since we want
1132 to use oval for our fast-bounds, rather than have to recompute it.
1133 */
1134 const SkScalar L = oval.fLeft; // cx - rx
1135 const SkScalar T = oval.fTop; // cy - ry
1136 const SkScalar R = oval.fRight; // cx + rx
1137 const SkScalar B = oval.fBottom; // cy + ry
1138
1139 this->incReserve(17); // 8 quads + close
1140 this->moveTo(R, cy);
1141 if (dir == kCCW_Direction) {
1142 this->quadTo( R, cy - sy, cx + mx, cy - my);
1143 this->quadTo(cx + sx, T, cx , T);
1144 this->quadTo(cx - sx, T, cx - mx, cy - my);
1145 this->quadTo( L, cy - sy, L, cy );
1146 this->quadTo( L, cy + sy, cx - mx, cy + my);
1147 this->quadTo(cx - sx, B, cx , B);
1148 this->quadTo(cx + sx, B, cx + mx, cy + my);
1149 this->quadTo( R, cy + sy, R, cy );
1150 } else {
1151 this->quadTo( R, cy + sy, cx + mx, cy + my);
1152 this->quadTo(cx + sx, B, cx , B);
1153 this->quadTo(cx - sx, B, cx - mx, cy + my);
1154 this->quadTo( L, cy + sy, L, cy );
1155 this->quadTo( L, cy - sy, cx - mx, cy - my);
1156 this->quadTo(cx - sx, T, cx , T);
1157 this->quadTo(cx + sx, T, cx + mx, cy - my);
1158 this->quadTo( R, cy - sy, R, cy );
1159 }
1160#endif
1161 this->close();
1162}
1163
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001164bool SkPath::isOval(SkRect* rect) const {
1165 if (fIsOval && rect) {
1166 *rect = getBounds();
1167 }
1168
1169 return fIsOval;
1170}
1171
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1173 if (r > 0) {
1174 SkRect rect;
1175 rect.set(x - r, y - r, x + r, y + r);
1176 this->addOval(rect, dir);
1177 }
1178}
1179
1180#include "SkGeometry.h"
1181
1182static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1183 SkScalar sweepAngle,
1184 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001185
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001186 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001187 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1188 // Chrome uses this path to move into and out of ovals. If not
1189 // treated as a special case the moves can distort the oval's
1190 // bounding box (and break the circle special case).
1191 pts[0].set(oval.fRight, oval.centerY());
1192 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001193 } else if (0 == oval.width() && 0 == oval.height()) {
1194 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001195 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001196 // a rect.
1197 // TODO: optimizing the case where only one of width or height is zero
1198 // should also be considered. This case, however, doesn't seem to be
1199 // as common as the single point case.
1200 pts[0].set(oval.fRight, oval.fTop);
1201 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001202 }
1203
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 SkVector start, stop;
1205
1206 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1207 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1208 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001209
1210 /* If the sweep angle is nearly (but less than) 360, then due to precision
1211 loss in radians-conversion and/or sin/cos, we may end up with coincident
1212 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1213 of drawing a nearly complete circle (good).
1214 e.g. canvas.drawArc(0, 359.99, ...)
1215 -vs- canvas.drawArc(0, 359.9, ...)
1216 We try to detect this edge case, and tweak the stop vector
1217 */
1218 if (start == stop) {
1219 SkScalar sw = SkScalarAbs(sweepAngle);
1220 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1221 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1222 // make a guess at a tiny angle (in radians) to tweak by
1223 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1224 // not sure how much will be enough, so we use a loop
1225 do {
1226 stopRad -= deltaRad;
1227 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1228 } while (start == stop);
1229 }
1230 }
1231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1235 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001236
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 return SkBuildQuadArc(start, stop,
1238 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1239 &matrix, pts);
1240}
1241
1242void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1243 bool forceMoveTo) {
1244 if (oval.width() < 0 || oval.height() < 0) {
1245 return;
1246 }
1247
1248 SkPoint pts[kSkBuildQuadArcStorage];
1249 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1250 SkASSERT((count & 1) == 1);
1251
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001252 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 forceMoveTo = true;
1254 }
1255 this->incReserve(count);
1256 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1257 for (int i = 1; i < count; i += 2) {
1258 this->quadTo(pts[i], pts[i+1]);
1259 }
1260}
1261
1262void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1263 SkScalar sweepAngle) {
1264 if (oval.isEmpty() || 0 == sweepAngle) {
1265 return;
1266 }
1267
1268 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1269
1270 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1271 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1272 return;
1273 }
1274
1275 SkPoint pts[kSkBuildQuadArcStorage];
1276 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1277
1278 this->incReserve(count);
1279 this->moveTo(pts[0]);
1280 for (int i = 1; i < count; i += 2) {
1281 this->quadTo(pts[i], pts[i+1]);
1282 }
1283}
1284
1285/*
1286 Need to handle the case when the angle is sharp, and our computed end-points
1287 for the arc go behind pt1 and/or p2...
1288*/
1289void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1290 SkScalar radius) {
1291 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001292
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 // need to know our prev pt so we can construct tangent vectors
1294 {
1295 SkPoint start;
1296 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001297 // Handle degenerate cases by adding a line to the first point and
1298 // bailing out.
1299 if ((x1 == start.fX && y1 == start.fY) ||
1300 (x1 == x2 && y1 == y2) ||
1301 radius == 0) {
1302 this->lineTo(x1, y1);
1303 return;
1304 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 before.setNormalize(x1 - start.fX, y1 - start.fY);
1306 after.setNormalize(x2 - x1, y2 - y1);
1307 }
reed@google.comabf15c12011-01-18 20:35:51 +00001308
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 SkScalar cosh = SkPoint::DotProduct(before, after);
1310 SkScalar sinh = SkPoint::CrossProduct(before, after);
1311
1312 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001313 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 return;
1315 }
reed@google.comabf15c12011-01-18 20:35:51 +00001316
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1318 if (dist < 0) {
1319 dist = -dist;
1320 }
1321
1322 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1323 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1324 SkRotationDirection arcDir;
1325
1326 // now turn before/after into normals
1327 if (sinh > 0) {
1328 before.rotateCCW();
1329 after.rotateCCW();
1330 arcDir = kCW_SkRotationDirection;
1331 } else {
1332 before.rotateCW();
1333 after.rotateCW();
1334 arcDir = kCCW_SkRotationDirection;
1335 }
1336
1337 SkMatrix matrix;
1338 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001339
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 matrix.setScale(radius, radius);
1341 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1342 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001343
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001345
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 this->incReserve(count);
1347 // [xx,yy] == pts[0]
1348 this->lineTo(xx, yy);
1349 for (int i = 1; i < count; i += 2) {
1350 this->quadTo(pts[i], pts[i+1]);
1351 }
1352}
1353
1354///////////////////////////////////////////////////////////////////////////////
1355
1356void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1357 SkMatrix matrix;
1358
1359 matrix.setTranslate(dx, dy);
1360 this->addPath(path, matrix);
1361}
1362
1363void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001364 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001366 fIsOval = false;
1367
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001368 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 SkPoint pts[4];
1370 Verb verb;
1371
1372 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1373
1374 while ((verb = iter.next(pts)) != kDone_Verb) {
1375 switch (verb) {
1376 case kMove_Verb:
1377 proc(matrix, &pts[0], &pts[0], 1);
1378 this->moveTo(pts[0]);
1379 break;
1380 case kLine_Verb:
1381 proc(matrix, &pts[1], &pts[1], 1);
1382 this->lineTo(pts[1]);
1383 break;
1384 case kQuad_Verb:
1385 proc(matrix, &pts[1], &pts[1], 2);
1386 this->quadTo(pts[1], pts[2]);
1387 break;
1388 case kCubic_Verb:
1389 proc(matrix, &pts[1], &pts[1], 3);
1390 this->cubicTo(pts[1], pts[2], pts[3]);
1391 break;
1392 case kClose_Verb:
1393 this->close();
1394 break;
1395 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001396 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001397 }
1398 }
1399}
1400
1401///////////////////////////////////////////////////////////////////////////////
1402
1403static const uint8_t gPtsInVerb[] = {
1404 1, // kMove
1405 1, // kLine
1406 2, // kQuad
1407 3, // kCubic
1408 0, // kClose
1409 0 // kDone
1410};
1411
1412// ignore the initial moveto, and stop when the 1st contour ends
1413void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001414 int i, vcount = path.fPathRef->countVerbs();
1415 // exit early if the path is empty, or just has a moveTo.
1416 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417 return;
1418 }
1419
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001420 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001422 fIsOval = false;
1423
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001424 const uint8_t* verbs = path.fPathRef->verbs();
1425 // skip the initial moveTo
1426 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001428 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001430 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 case kLine_Verb:
1432 this->lineTo(pts[0].fX, pts[0].fY);
1433 break;
1434 case kQuad_Verb:
1435 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1436 break;
1437 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001438 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 +00001439 break;
1440 case kClose_Verb:
1441 return;
1442 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001443 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 }
1445}
1446
1447// ignore the last point of the 1st contour
1448void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001449 int i, vcount = path.fPathRef->countVerbs();
1450 // exit early if the path is empty, or just has a moveTo.
1451 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 return;
1453 }
1454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001455 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001457 fIsOval = false;
1458
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001459 const uint8_t* verbs = path.fPathRef->verbs();
1460 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 SkASSERT(verbs[~0] == kMove_Verb);
1463 for (i = 1; i < vcount; ++i) {
1464 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 if (n == 0) {
1466 break;
1467 }
1468 pts += n;
1469 }
1470
1471 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 case kLine_Verb:
1474 this->lineTo(pts[-1].fX, pts[-1].fY);
1475 break;
1476 case kQuad_Verb:
1477 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1478 break;
1479 case kCubic_Verb:
1480 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1481 pts[-3].fX, pts[-3].fY);
1482 break;
1483 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001484 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 break;
1486 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001487 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 }
1489}
1490
reed@google.com63d73742012-01-10 15:33:12 +00001491void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001492 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001493
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001494 const SkPoint* pts = src.fPathRef->pointsEnd();
1495 // we will iterator through src's verbs backwards
1496 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1497 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001498
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001499 fIsOval = false;
1500
reed@google.com63d73742012-01-10 15:33:12 +00001501 bool needMove = true;
1502 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001503 while (verbs < verbsEnd) {
1504 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001505 int n = gPtsInVerb[v];
1506
1507 if (needMove) {
1508 --pts;
1509 this->moveTo(pts->fX, pts->fY);
1510 needMove = false;
1511 }
1512 pts -= n;
1513 switch (v) {
1514 case kMove_Verb:
1515 if (needClose) {
1516 this->close();
1517 needClose = false;
1518 }
1519 needMove = true;
1520 pts += 1; // so we see the point in "if (needMove)" above
1521 break;
1522 case kLine_Verb:
1523 this->lineTo(pts[0]);
1524 break;
1525 case kQuad_Verb:
1526 this->quadTo(pts[1], pts[0]);
1527 break;
1528 case kCubic_Verb:
1529 this->cubicTo(pts[2], pts[1], pts[0]);
1530 break;
1531 case kClose_Verb:
1532 needClose = true;
1533 break;
1534 default:
1535 SkASSERT(!"unexpected verb");
1536 }
1537 }
1538}
1539
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540///////////////////////////////////////////////////////////////////////////////
1541
1542void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1543 SkMatrix matrix;
1544
1545 matrix.setTranslate(dx, dy);
1546 this->transform(matrix, dst);
1547}
1548
1549#include "SkGeometry.h"
1550
1551static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1552 int level = 2) {
1553 if (--level >= 0) {
1554 SkPoint tmp[5];
1555
1556 SkChopQuadAtHalf(pts, tmp);
1557 subdivide_quad_to(path, &tmp[0], level);
1558 subdivide_quad_to(path, &tmp[2], level);
1559 } else {
1560 path->quadTo(pts[1], pts[2]);
1561 }
1562}
1563
1564static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1565 int level = 2) {
1566 if (--level >= 0) {
1567 SkPoint tmp[7];
1568
1569 SkChopCubicAtHalf(pts, tmp);
1570 subdivide_cubic_to(path, &tmp[0], level);
1571 subdivide_cubic_to(path, &tmp[3], level);
1572 } else {
1573 path->cubicTo(pts[1], pts[2], pts[3]);
1574 }
1575}
1576
1577void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1578 SkDEBUGCODE(this->validate();)
1579 if (dst == NULL) {
1580 dst = (SkPath*)this;
1581 }
1582
tomhudson@google.com8d430182011-06-06 19:11:19 +00001583 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 SkPath tmp;
1585 tmp.fFillType = fFillType;
1586
1587 SkPath::Iter iter(*this, false);
1588 SkPoint pts[4];
1589 SkPath::Verb verb;
1590
reed@google.com4a3b7142012-05-16 17:16:46 +00001591 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 switch (verb) {
1593 case kMove_Verb:
1594 tmp.moveTo(pts[0]);
1595 break;
1596 case kLine_Verb:
1597 tmp.lineTo(pts[1]);
1598 break;
1599 case kQuad_Verb:
1600 subdivide_quad_to(&tmp, pts);
1601 break;
1602 case kCubic_Verb:
1603 subdivide_cubic_to(&tmp, pts);
1604 break;
1605 case kClose_Verb:
1606 tmp.close();
1607 break;
1608 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001609 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 break;
1611 }
1612 }
1613
1614 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001615 SkPathRef::Editor ed(&dst->fPathRef);
1616 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001617 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001619 /*
1620 * If we're not in perspective, we can transform all of the points at
1621 * once.
1622 *
1623 * Here we also want to optimize bounds, by noting if the bounds are
1624 * already known, and if so, we just transform those as well and mark
1625 * them as "known", rather than force the transformed path to have to
1626 * recompute them.
1627 *
1628 * Special gotchas if the path is effectively empty (<= 1 point) or
1629 * if it is non-finite. In those cases bounds need to stay empty,
1630 * regardless of the matrix.
1631 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001632 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001633 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001634 if (fIsFinite) {
1635 matrix.mapRect(&dst->fBounds, fBounds);
1636 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1637 dst->fBounds.setEmpty();
1638 }
1639 } else {
1640 dst->fIsFinite = false;
1641 dst->fBounds.setEmpty();
1642 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001644 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001645 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 }
1647
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001648 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1649
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001652 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001653 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001655
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001656 if (kUnknown_Direction == fDirection) {
1657 dst->fDirection = kUnknown_Direction;
1658 } else {
1659 SkScalar det2x2 =
1660 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1661 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1662 if (det2x2 < 0) {
1663 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1664 } else if (det2x2 > 0) {
1665 dst->fDirection = fDirection;
1666 } else {
1667 dst->fDirection = kUnknown_Direction;
1668 }
1669 }
1670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001671 // It's an oval only if it stays a rect.
1672 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001673
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 SkDEBUGCODE(dst->validate();)
1675 }
1676}
1677
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678///////////////////////////////////////////////////////////////////////////////
1679///////////////////////////////////////////////////////////////////////////////
1680
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001681enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001682 kEmptyContour_SegmentState, // The current contour is empty. We may be
1683 // starting processing or we may have just
1684 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001685 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1686 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1687 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688};
1689
1690SkPath::Iter::Iter() {
1691#ifdef SK_DEBUG
1692 fPts = NULL;
1693 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001694 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001695 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696#endif
1697 // need to init enough to make next() harmlessly return kDone_Verb
1698 fVerbs = NULL;
1699 fVerbStop = NULL;
1700 fNeedClose = false;
1701}
1702
1703SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1704 this->setPath(path, forceClose);
1705}
1706
1707void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001708 fPts = path.fPathRef->points();
1709 fVerbs = path.fPathRef->verbs();
1710 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001711 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001712 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713 fForceClose = SkToU8(forceClose);
1714 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001715 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716}
1717
1718bool SkPath::Iter::isClosedContour() const {
1719 if (fVerbs == NULL || fVerbs == fVerbStop) {
1720 return false;
1721 }
1722 if (fForceClose) {
1723 return true;
1724 }
1725
1726 const uint8_t* verbs = fVerbs;
1727 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001728
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001729 if (kMove_Verb == *(verbs - 1)) {
1730 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 }
1732
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 while (verbs > stop) {
1734 // verbs points one beyond the current verb, decrement first.
1735 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 if (kMove_Verb == v) {
1737 break;
1738 }
1739 if (kClose_Verb == v) {
1740 return true;
1741 }
1742 }
1743 return false;
1744}
1745
1746SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001747 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001749 // A special case: if both points are NaN, SkPoint::operation== returns
1750 // false, but the iterator expects that they are treated as the same.
1751 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001752 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1753 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001754 return kClose_Verb;
1755 }
1756
reed@google.com9e25dbf2012-05-15 17:05:38 +00001757 pts[0] = fLastPt;
1758 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 fLastPt = fMoveTo;
1760 fCloseLine = true;
1761 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001762 } else {
1763 pts[0] = fMoveTo;
1764 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766}
1767
reed@google.com9e25dbf2012-05-15 17:05:38 +00001768const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 if (fSegmentState == kAfterMove_SegmentState) {
1770 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001771 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001772 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1775 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001776 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778}
1779
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001780void SkPath::Iter::consumeDegenerateSegments() {
1781 // We need to step over anything that will not move the current draw point
1782 // forward before the next move is seen
1783 const uint8_t* lastMoveVerb = 0;
1784 const SkPoint* lastMovePt = 0;
1785 SkPoint lastPt = fLastPt;
1786 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001787 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 switch (verb) {
1789 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001790 // Keep a record of this most recent move
1791 lastMoveVerb = fVerbs;
1792 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001793 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001794 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001795 fPts++;
1796 break;
1797
1798 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001799 // A close when we are in a segment is always valid except when it
1800 // follows a move which follows a segment.
1801 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 return;
1803 }
1804 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001805 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 break;
1807
1808 case kLine_Verb:
1809 if (!IsLineDegenerate(lastPt, fPts[0])) {
1810 if (lastMoveVerb) {
1811 fVerbs = lastMoveVerb;
1812 fPts = lastMovePt;
1813 return;
1814 }
1815 return;
1816 }
1817 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001818 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 fPts++;
1820 break;
1821
1822 case kQuad_Verb:
1823 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1824 if (lastMoveVerb) {
1825 fVerbs = lastMoveVerb;
1826 fPts = lastMovePt;
1827 return;
1828 }
1829 return;
1830 }
1831 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 fPts += 2;
1834 break;
1835
1836 case kCubic_Verb:
1837 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1838 if (lastMoveVerb) {
1839 fVerbs = lastMoveVerb;
1840 fPts = lastMovePt;
1841 return;
1842 }
1843 return;
1844 }
1845 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001846 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 fPts += 3;
1848 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001849
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001851 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 }
1853 }
1854}
1855
reed@google.com4a3b7142012-05-16 17:16:46 +00001856SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001857 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 // Close the curve if requested and if there is some curve to close
1861 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001862 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 return kLine_Verb;
1864 }
1865 fNeedClose = false;
1866 return kClose_Verb;
1867 }
1868 return kDone_Verb;
1869 }
1870
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001871 // fVerbs is one beyond the current verb, decrement first
1872 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 const SkPoint* SK_RESTRICT srcPts = fPts;
1874 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875
1876 switch (verb) {
1877 case kMove_Verb:
1878 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001879 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 verb = this->autoClose(pts);
1881 if (verb == kClose_Verb) {
1882 fNeedClose = false;
1883 }
1884 return (Verb)verb;
1885 }
1886 if (fVerbs == fVerbStop) { // might be a trailing moveto
1887 return kDone_Verb;
1888 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001889 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001890 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001892 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 fNeedClose = fForceClose;
1895 break;
1896 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897 pts[0] = this->cons_moveTo();
1898 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 fLastPt = srcPts[0];
1900 fCloseLine = false;
1901 srcPts += 1;
1902 break;
1903 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001904 pts[0] = this->cons_moveTo();
1905 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 fLastPt = srcPts[1];
1907 srcPts += 2;
1908 break;
1909 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 pts[0] = this->cons_moveTo();
1911 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 fLastPt = srcPts[2];
1913 srcPts += 3;
1914 break;
1915 case kClose_Verb:
1916 verb = this->autoClose(pts);
1917 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 } else {
1920 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001921 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001922 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001924 break;
1925 }
1926 fPts = srcPts;
1927 return (Verb)verb;
1928}
1929
1930///////////////////////////////////////////////////////////////////////////////
1931
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001932SkPath::RawIter::RawIter() {
1933#ifdef SK_DEBUG
1934 fPts = NULL;
1935 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1936#endif
1937 // need to init enough to make next() harmlessly return kDone_Verb
1938 fVerbs = NULL;
1939 fVerbStop = NULL;
1940}
1941
1942SkPath::RawIter::RawIter(const SkPath& path) {
1943 this->setPath(path);
1944}
1945
1946void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 fPts = path.fPathRef->points();
1948 fVerbs = path.fPathRef->verbs();
1949 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001950 fMoveTo.fX = fMoveTo.fY = 0;
1951 fLastPt.fX = fLastPt.fY = 0;
1952}
1953
1954SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001955 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001956 if (fVerbs == fVerbStop) {
1957 return kDone_Verb;
1958 }
1959
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001960 // fVerbs points one beyond next verb so decrement first.
1961 unsigned verb = *(--fVerbs);
1962 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001963
1964 switch (verb) {
1965 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001966 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001967 fMoveTo = srcPts[0];
1968 fLastPt = fMoveTo;
1969 srcPts += 1;
1970 break;
1971 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001972 pts[0] = fLastPt;
1973 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001974 fLastPt = srcPts[0];
1975 srcPts += 1;
1976 break;
1977 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001978 pts[0] = fLastPt;
1979 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001980 fLastPt = srcPts[1];
1981 srcPts += 2;
1982 break;
1983 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001984 pts[0] = fLastPt;
1985 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001986 fLastPt = srcPts[2];
1987 srcPts += 3;
1988 break;
1989 case kClose_Verb:
1990 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001991 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992 break;
1993 }
1994 fPts = srcPts;
1995 return (Verb)verb;
1996}
1997
1998///////////////////////////////////////////////////////////////////////////////
1999
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002001 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002*/
2003
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002004uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 SkDEBUGCODE(this->validate();)
2006
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002007 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002008 const int byteCount = sizeof(int32_t)
2009#if NEW_PICTURE_FORMAT
2010 + fPathRef->writeSize()
2011#else
2012 + 2 * sizeof(int32_t)
2013 + sizeof(SkPoint) * fPathRef->countPoints()
2014 + sizeof(uint8_t) * fPathRef->countVerbs()
2015#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002016 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002017 return SkAlign4(byteCount);
2018 }
2019
2020 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002021#if !NEW_PICTURE_FORMAT
2022 buffer.write32(fPathRef->countPoints());
2023 buffer.write32(fPathRef->countVerbs());
2024#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002025
2026 // Call getBounds() to ensure (as a side-effect) that fBounds
2027 // and fIsFinite are computed.
2028 const SkRect& bounds = this->getBounds();
2029 SkASSERT(!fBoundsIsDirty);
2030
2031 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2032 ((fIsOval & 1) << kIsOval_SerializationShift) |
2033 (fConvexity << kConvexity_SerializationShift) |
2034 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002035 (fSegmentMask << kSegmentMask_SerializationShift) |
2036 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002037
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002038 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002039
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002040 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002041
2042 buffer.write(&bounds, sizeof(bounds));
2043
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002044 buffer.padToAlign4();
2045 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046}
2047
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002048uint32_t SkPath::readFromMemory(const void* storage) {
2049 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002050#if !NEW_PICTURE_FORMAT
2051 int32_t pcount = buffer.readS32();
2052 int32_t vcount = buffer.readS32();
2053#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002054
reed@google.com98b11f12011-09-21 18:40:27 +00002055 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002056 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2057 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2058 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2059 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002060 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2061 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002062
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002063#if NEW_PICTURE_FORMAT
2064 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2065#else
2066 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2067#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002068
2069 buffer.read(&fBounds, sizeof(fBounds));
2070 fBoundsIsDirty = false;
2071
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002072 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002073
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002074 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002075
2076 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002077 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078}
2079
2080///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082void SkPath::dump(bool forceClose, const char title[]) const {
2083 Iter iter(*this, forceClose);
2084 SkPoint pts[4];
2085 Verb verb;
2086
2087 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2088 title ? title : "");
2089
reed@google.com4a3b7142012-05-16 17:16:46 +00002090 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002091 switch (verb) {
2092 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 SkDebugf(" path: moveTo [%g %g]\n",
2094 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095 break;
2096 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097 SkDebugf(" path: lineTo [%g %g]\n",
2098 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 break;
2100 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2102 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2103 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002104 break;
2105 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2107 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2108 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2109 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110 break;
2111 case kClose_Verb:
2112 SkDebugf(" path: close\n");
2113 break;
2114 default:
2115 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2116 verb = kDone_Verb; // stop the loop
2117 break;
2118 }
2119 }
2120 SkDebugf("path: done %s\n", title ? title : "");
2121}
2122
reed@android.come522ca52009-11-23 20:10:41 +00002123void SkPath::dump() const {
2124 this->dump(false);
2125}
2126
2127#ifdef SK_DEBUG
2128void SkPath::validate() const {
2129 SkASSERT(this != NULL);
2130 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002131
djsollen@google.com077348c2012-10-22 20:23:32 +00002132#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002133 if (!fBoundsIsDirty) {
2134 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002135
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002136 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002137 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002138
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002139 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002140 // if we're empty, fBounds may be empty but translated, so we can't
2141 // necessarily compare to bounds directly
2142 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2143 // be [2, 2, 2, 2]
2144 SkASSERT(bounds.isEmpty());
2145 SkASSERT(fBounds.isEmpty());
2146 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002147 if (bounds.isEmpty()) {
2148 SkASSERT(fBounds.isEmpty());
2149 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002150 if (!fBounds.isEmpty()) {
2151 SkASSERT(fBounds.contains(bounds));
2152 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002153 }
reed@android.come522ca52009-11-23 20:10:41 +00002154 }
2155 }
reed@google.com10296cc2011-09-21 12:29:05 +00002156
2157 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002158 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2159 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2160 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002161 case kLine_Verb:
2162 mask |= kLine_SegmentMask;
2163 break;
2164 case kQuad_Verb:
2165 mask |= kQuad_SegmentMask;
2166 break;
2167 case kCubic_Verb:
2168 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002169 case kMove_Verb: // these verbs aren't included in the segment mask.
2170 case kClose_Verb:
2171 break;
2172 case kDone_Verb:
2173 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2174 break;
2175 default:
2176 SkDEBUGFAIL("Unknown Verb");
2177 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002178 }
2179 }
2180 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002181#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002182}
djsollen@google.com077348c2012-10-22 20:23:32 +00002183#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002184
reed@google.com04863fa2011-05-15 04:08:24 +00002185///////////////////////////////////////////////////////////////////////////////
2186
reed@google.com0b7b9822011-05-16 12:29:27 +00002187static int sign(SkScalar x) { return x < 0; }
2188#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002189
reed@google.com04863fa2011-05-15 04:08:24 +00002190static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002191 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002192}
2193
2194// only valid for a single contour
2195struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002196 Convexicator()
2197 : fPtCount(0)
2198 , fConvexity(SkPath::kConvex_Convexity)
2199 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002200 fSign = 0;
2201 // warnings
2202 fCurrPt.set(0, 0);
2203 fVec0.set(0, 0);
2204 fVec1.set(0, 0);
2205 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002206
2207 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002208 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002209 }
2210
2211 SkPath::Convexity getConvexity() const { return fConvexity; }
2212
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002213 /** The direction returned is only valid if the path is determined convex */
2214 SkPath::Direction getDirection() const { return fDirection; }
2215
reed@google.com04863fa2011-05-15 04:08:24 +00002216 void addPt(const SkPoint& pt) {
2217 if (SkPath::kConcave_Convexity == fConvexity) {
2218 return;
2219 }
2220
2221 if (0 == fPtCount) {
2222 fCurrPt = pt;
2223 ++fPtCount;
2224 } else {
2225 SkVector vec = pt - fCurrPt;
2226 if (vec.fX || vec.fY) {
2227 fCurrPt = pt;
2228 if (++fPtCount == 2) {
2229 fFirstVec = fVec1 = vec;
2230 } else {
2231 SkASSERT(fPtCount > 2);
2232 this->addVec(vec);
2233 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002234
reed@google.com85b6e392011-05-15 20:25:17 +00002235 int sx = sign(vec.fX);
2236 int sy = sign(vec.fY);
2237 fDx += (sx != fSx);
2238 fDy += (sy != fSy);
2239 fSx = sx;
2240 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002241
reed@google.com85b6e392011-05-15 20:25:17 +00002242 if (fDx > 3 || fDy > 3) {
2243 fConvexity = SkPath::kConcave_Convexity;
2244 }
reed@google.com04863fa2011-05-15 04:08:24 +00002245 }
2246 }
2247 }
2248
2249 void close() {
2250 if (fPtCount > 2) {
2251 this->addVec(fFirstVec);
2252 }
2253 }
2254
2255private:
2256 void addVec(const SkVector& vec) {
2257 SkASSERT(vec.fX || vec.fY);
2258 fVec0 = fVec1;
2259 fVec1 = vec;
2260 int sign = CrossProductSign(fVec0, fVec1);
2261 if (0 == fSign) {
2262 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002263 if (1 == sign) {
2264 fDirection = SkPath::kCW_Direction;
2265 } else if (-1 == sign) {
2266 fDirection = SkPath::kCCW_Direction;
2267 }
reed@google.com04863fa2011-05-15 04:08:24 +00002268 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002269 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002270 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002271 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002272 }
2273 }
2274 }
2275
2276 SkPoint fCurrPt;
2277 SkVector fVec0, fVec1, fFirstVec;
2278 int fPtCount; // non-degenerate points
2279 int fSign;
2280 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002281 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002282 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002283};
2284
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285SkPath::Convexity SkPath::internalGetConvexity() const {
2286 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002287 SkPoint pts[4];
2288 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002289 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002290
2291 int contourCount = 0;
2292 int count;
2293 Convexicator state;
2294
2295 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2296 switch (verb) {
2297 case kMove_Verb:
2298 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002300 return kConcave_Convexity;
2301 }
2302 pts[1] = pts[0];
2303 count = 1;
2304 break;
2305 case kLine_Verb: count = 1; break;
2306 case kQuad_Verb: count = 2; break;
2307 case kCubic_Verb: count = 3; break;
2308 case kClose_Verb:
2309 state.close();
2310 count = 0;
2311 break;
2312 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002313 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002314 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002315 return kConcave_Convexity;
2316 }
2317
2318 for (int i = 1; i <= count; i++) {
2319 state.addPt(pts[i]);
2320 }
2321 // early exit
2322 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002323 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002324 return kConcave_Convexity;
2325 }
2326 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002327 fConvexity = state.getConvexity();
2328 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2329 fDirection = state.getDirection();
2330 }
2331 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002332}
reed@google.com69a99432012-01-10 18:00:10 +00002333
2334///////////////////////////////////////////////////////////////////////////////
2335
2336class ContourIter {
2337public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002338 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002339
2340 bool done() const { return fDone; }
2341 // if !done() then these may be called
2342 int count() const { return fCurrPtCount; }
2343 const SkPoint* pts() const { return fCurrPt; }
2344 void next();
2345
2346private:
2347 int fCurrPtCount;
2348 const SkPoint* fCurrPt;
2349 const uint8_t* fCurrVerb;
2350 const uint8_t* fStopVerbs;
2351 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002352 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002353};
2354
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002355ContourIter::ContourIter(const SkPathRef& pathRef) {
2356 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002357 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002358 fCurrPt = pathRef.points();
2359 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002360 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002361 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002362 this->next();
2363}
2364
2365void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002366 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002367 fDone = true;
2368 }
2369 if (fDone) {
2370 return;
2371 }
2372
2373 // skip pts of prev contour
2374 fCurrPt += fCurrPtCount;
2375
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002376 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002377 int ptCount = 1; // moveTo
2378 const uint8_t* verbs = fCurrVerb;
2379
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002380 for (--verbs; verbs > fStopVerbs; --verbs) {
2381 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002382 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002383 goto CONTOUR_END;
2384 case SkPath::kLine_Verb:
2385 ptCount += 1;
2386 break;
2387 case SkPath::kQuad_Verb:
2388 ptCount += 2;
2389 break;
2390 case SkPath::kCubic_Verb:
2391 ptCount += 3;
2392 break;
2393 default: // kClose_Verb, just keep going
2394 break;
2395 }
2396 }
2397CONTOUR_END:
2398 fCurrPtCount = ptCount;
2399 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002400 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002401}
2402
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002403// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002404static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002405 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2406 // We may get 0 when the above subtracts underflow. We expect this to be
2407 // very rare and lazily promote to double.
2408 if (0 == cross) {
2409 double p0x = SkScalarToDouble(p0.fX);
2410 double p0y = SkScalarToDouble(p0.fY);
2411
2412 double p1x = SkScalarToDouble(p1.fX);
2413 double p1y = SkScalarToDouble(p1.fY);
2414
2415 double p2x = SkScalarToDouble(p2.fX);
2416 double p2y = SkScalarToDouble(p2.fY);
2417
2418 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2419 (p1y - p0y) * (p2x - p0x));
2420
2421 }
2422 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002423}
2424
reed@google.comc1ea60a2012-01-31 15:15:36 +00002425// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002426static int find_max_y(const SkPoint pts[], int count) {
2427 SkASSERT(count > 0);
2428 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002429 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002430 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002431 SkScalar y = pts[i].fY;
2432 if (y > max) {
2433 max = y;
2434 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002435 }
2436 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002437 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002438}
2439
reed@google.comcabaf1d2012-01-11 21:03:05 +00002440static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2441 int i = index;
2442 for (;;) {
2443 i = (i + inc) % n;
2444 if (i == index) { // we wrapped around, so abort
2445 break;
2446 }
2447 if (pts[index] != pts[i]) { // found a different point, success!
2448 break;
2449 }
2450 }
2451 return i;
2452}
2453
reed@google.comc1ea60a2012-01-31 15:15:36 +00002454/**
2455 * Starting at index, and moving forward (incrementing), find the xmin and
2456 * xmax of the contiguous points that have the same Y.
2457 */
2458static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2459 int* maxIndexPtr) {
2460 const SkScalar y = pts[index].fY;
2461 SkScalar min = pts[index].fX;
2462 SkScalar max = min;
2463 int minIndex = index;
2464 int maxIndex = index;
2465 for (int i = index + 1; i < n; ++i) {
2466 if (pts[i].fY != y) {
2467 break;
2468 }
2469 SkScalar x = pts[i].fX;
2470 if (x < min) {
2471 min = x;
2472 minIndex = i;
2473 } else if (x > max) {
2474 max = x;
2475 maxIndex = i;
2476 }
2477 }
2478 *maxIndexPtr = maxIndex;
2479 return minIndex;
2480}
2481
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002482static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002483 if (dir) {
2484 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2485 }
reed@google.comac8543f2012-01-30 20:51:25 +00002486}
2487
reed@google.comc1ea60a2012-01-31 15:15:36 +00002488#if 0
2489#include "SkString.h"
2490#include "../utils/SkParsePath.h"
2491static void dumpPath(const SkPath& path) {
2492 SkString str;
2493 SkParsePath::ToSVGString(path, &str);
2494 SkDebugf("%s\n", str.c_str());
2495}
2496#endif
2497
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002498namespace {
2499// for use with convex_dir_test
2500double mul(double a, double b) { return a * b; }
2501SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2502double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2503SkScalar toScalar(SkScalar a) { return a; }
2504
2505// determines the winding direction of a convex polygon with the precision
2506// of T. CAST_SCALAR casts an SkScalar to T.
2507template <typename T, T (CAST_SCALAR)(SkScalar)>
2508bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2509 // we find the first three points that form a non-degenerate
2510 // triangle. If there are no such points then the path is
2511 // degenerate. The first is always point 0. Now we find the second
2512 // point.
2513 int i = 0;
2514 enum { kX = 0, kY = 1 };
2515 T v0[2];
2516 while (1) {
2517 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2518 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2519 if (v0[kX] || v0[kY]) {
2520 break;
2521 }
2522 if (++i == n - 1) {
2523 return false;
2524 }
2525 }
2526 // now find a third point that is not colinear with the first two
2527 // points and check the orientation of the triangle (which will be
2528 // the same as the orientation of the path).
2529 for (++i; i < n; ++i) {
2530 T v1[2];
2531 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2532 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2533 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2534 if (0 != cross) {
2535 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2536 return true;
2537 }
2538 }
2539 return false;
2540}
2541}
2542
reed@google.comac8543f2012-01-30 20:51:25 +00002543/*
2544 * We loop through all contours, and keep the computed cross-product of the
2545 * contour that contained the global y-max. If we just look at the first
2546 * contour, we may find one that is wound the opposite way (correctly) since
2547 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2548 * that is outer most (or at least has the global y-max) before we can consider
2549 * its cross product.
2550 */
reed@google.com69a99432012-01-10 18:00:10 +00002551bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002552// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002553 // don't want to pay the cost for computing this if it
2554 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002555
2556 if (kUnknown_Direction != fDirection) {
2557 *dir = static_cast<Direction>(fDirection);
2558 return true;
2559 }
reed@google.com69a99432012-01-10 18:00:10 +00002560 const Convexity conv = this->getConvexityOrUnknown();
2561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002562 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002563
reed@google.comac8543f2012-01-30 20:51:25 +00002564 // initialize with our logical y-min
2565 SkScalar ymax = this->getBounds().fTop;
2566 SkScalar ymaxCross = 0;
2567
reed@google.com69a99432012-01-10 18:00:10 +00002568 for (; !iter.done(); iter.next()) {
2569 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002570 if (n < 3) {
2571 continue;
2572 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002573
reed@google.comcabaf1d2012-01-11 21:03:05 +00002574 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002575 SkScalar cross = 0;
2576 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002577 // We try first at scalar precision, and then again at double
2578 // precision. This is because the vectors computed between distant
2579 // points may lose too much precision.
2580 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002581 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002582 return true;
2583 }
2584 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002585 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002586 return true;
2587 } else {
2588 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002589 }
2590 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002591 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002592 if (pts[index].fY < ymax) {
2593 continue;
2594 }
2595
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596 // If there is more than 1 distinct point at the y-max, we take the
2597 // x-min and x-max of them and just subtract to compute the dir.
2598 if (pts[(index + 1) % n].fY == pts[index].fY) {
2599 int maxIndex;
2600 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002601 if (minIndex == maxIndex) {
2602 goto TRY_CROSSPROD;
2603 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002604 SkASSERT(pts[minIndex].fY == pts[index].fY);
2605 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2606 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2607 // we just subtract the indices, and let that auto-convert to
2608 // SkScalar, since we just want - or + to signal the direction.
2609 cross = minIndex - maxIndex;
2610 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002611 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002612 // Find a next and prev index to use for the cross-product test,
2613 // but we try to find pts that form non-zero vectors from pts[index]
2614 //
2615 // Its possible that we can't find two non-degenerate vectors, so
2616 // we have to guard our search (e.g. all the pts could be in the
2617 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002618
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619 // we pass n - 1 instead of -1 so we don't foul up % operator by
2620 // passing it a negative LH argument.
2621 int prev = find_diff_pt(pts, index, n, n - 1);
2622 if (prev == index) {
2623 // completely degenerate, skip to next contour
2624 continue;
2625 }
2626 int next = find_diff_pt(pts, index, n, 1);
2627 SkASSERT(next != index);
2628 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002629 // 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 +00002630 // x-direction. We really should continue to walk away from the degeneracy until
2631 // there is a divergence.
2632 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002633 // construct the subtract so we get the correct Direction below
2634 cross = pts[index].fX - pts[next].fX;
2635 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002636 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002637
reed@google.comac8543f2012-01-30 20:51:25 +00002638 if (cross) {
2639 // record our best guess so far
2640 ymax = pts[index].fY;
2641 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002642 }
reed@google.com69a99432012-01-10 18:00:10 +00002643 }
2644 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002645 if (ymaxCross) {
2646 crossToDir(ymaxCross, dir);
2647 fDirection = *dir;
2648 return true;
2649 } else {
2650 return false;
2651 }
reed@google.comac8543f2012-01-30 20:51:25 +00002652}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002653
2654///////////////////////////////////////////////////////////////////////////////
2655
2656static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2657 SkScalar D, SkScalar t) {
2658 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2659}
2660
2661static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2662 SkScalar t) {
2663 SkScalar A = c3 + 3*(c1 - c2) - c0;
2664 SkScalar B = 3*(c2 - c1 - c1 + c0);
2665 SkScalar C = 3*(c1 - c0);
2666 SkScalar D = c0;
2667 return eval_cubic_coeff(A, B, C, D, t);
2668}
2669
2670/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2671 t value such that cubic(t) = target
2672 */
2673static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2674 SkScalar target, SkScalar* t) {
2675 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2676 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002677
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002678 SkScalar D = c0 - target;
2679 SkScalar A = c3 + 3*(c1 - c2) - c0;
2680 SkScalar B = 3*(c2 - c1 - c1 + c0);
2681 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002682
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002683 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2684 SkScalar minT = 0;
2685 SkScalar maxT = SK_Scalar1;
2686 SkScalar mid;
2687 int i;
2688 for (i = 0; i < 16; i++) {
2689 mid = SkScalarAve(minT, maxT);
2690 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2691 if (delta < 0) {
2692 minT = mid;
2693 delta = -delta;
2694 } else {
2695 maxT = mid;
2696 }
2697 if (delta < TOLERANCE) {
2698 break;
2699 }
2700 }
2701 *t = mid;
2702 return true;
2703}
2704
2705template <size_t N> static void find_minmax(const SkPoint pts[],
2706 SkScalar* minPtr, SkScalar* maxPtr) {
2707 SkScalar min, max;
2708 min = max = pts[0].fX;
2709 for (size_t i = 1; i < N; ++i) {
2710 min = SkMinScalar(min, pts[i].fX);
2711 max = SkMaxScalar(max, pts[i].fX);
2712 }
2713 *minPtr = min;
2714 *maxPtr = max;
2715}
2716
2717static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2718 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002719
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002720 int dir = 1;
2721 if (pts[0].fY > pts[3].fY) {
2722 storage[0] = pts[3];
2723 storage[1] = pts[2];
2724 storage[2] = pts[1];
2725 storage[3] = pts[0];
2726 pts = storage;
2727 dir = -1;
2728 }
2729 if (y < pts[0].fY || y >= pts[3].fY) {
2730 return 0;
2731 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002733 // quickreject or quickaccept
2734 SkScalar min, max;
2735 find_minmax<4>(pts, &min, &max);
2736 if (x < min) {
2737 return 0;
2738 }
2739 if (x > max) {
2740 return dir;
2741 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002742
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002743 // compute the actual x(t) value
2744 SkScalar t, xt;
2745 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2746 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2747 } else {
2748 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2749 xt = y < mid ? pts[0].fX : pts[3].fX;
2750 }
2751 return xt < x ? dir : 0;
2752}
2753
2754static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2755 SkPoint dst[10];
2756 int n = SkChopCubicAtYExtrema(pts, dst);
2757 int w = 0;
2758 for (int i = 0; i <= n; ++i) {
2759 w += winding_mono_cubic(&dst[i * 3], x, y);
2760 }
2761 return w;
2762}
2763
2764static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2765 SkScalar y0 = pts[0].fY;
2766 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002767
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768 int dir = 1;
2769 if (y0 > y2) {
2770 SkTSwap(y0, y2);
2771 dir = -1;
2772 }
2773 if (y < y0 || y >= y2) {
2774 return 0;
2775 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002776
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002777 // bounds check on X (not required. is it faster?)
2778#if 0
2779 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2780 return 0;
2781 }
2782#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002783
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002784 SkScalar roots[2];
2785 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2786 2 * (pts[1].fY - pts[0].fY),
2787 pts[0].fY - y,
2788 roots);
2789 SkASSERT(n <= 1);
2790 SkScalar xt;
2791 if (0 == n) {
2792 SkScalar mid = SkScalarAve(y0, y2);
2793 // Need [0] and [2] if dir == 1
2794 // and [2] and [0] if dir == -1
2795 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2796 } else {
2797 SkScalar t = roots[0];
2798 SkScalar C = pts[0].fX;
2799 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2800 SkScalar B = 2 * (pts[1].fX - C);
2801 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2802 }
2803 return xt < x ? dir : 0;
2804}
2805
2806static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2807 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2808 if (y0 == y1) {
2809 return true;
2810 }
2811 if (y0 < y1) {
2812 return y1 <= y2;
2813 } else {
2814 return y1 >= y2;
2815 }
2816}
2817
2818static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2819 SkPoint dst[5];
2820 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2823 n = SkChopQuadAtYExtrema(pts, dst);
2824 pts = dst;
2825 }
2826 int w = winding_mono_quad(pts, x, y);
2827 if (n > 0) {
2828 w += winding_mono_quad(&pts[2], x, y);
2829 }
2830 return w;
2831}
2832
2833static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2834 SkScalar x0 = pts[0].fX;
2835 SkScalar y0 = pts[0].fY;
2836 SkScalar x1 = pts[1].fX;
2837 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002838
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 int dir = 1;
2842 if (y0 > y1) {
2843 SkTSwap(y0, y1);
2844 dir = -1;
2845 }
2846 if (y < y0 || y >= y1) {
2847 return 0;
2848 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002849
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002850 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2851 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002852
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 if (SkScalarSignAsInt(cross) == dir) {
2854 dir = 0;
2855 }
2856 return dir;
2857}
2858
2859bool SkPath::contains(SkScalar x, SkScalar y) const {
2860 bool isInverse = this->isInverseFillType();
2861 if (this->isEmpty()) {
2862 return isInverse;
2863 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002864
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865 const SkRect& bounds = this->getBounds();
2866 if (!bounds.contains(x, y)) {
2867 return isInverse;
2868 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002869
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870 SkPath::Iter iter(*this, true);
2871 bool done = false;
2872 int w = 0;
2873 do {
2874 SkPoint pts[4];
2875 switch (iter.next(pts, false)) {
2876 case SkPath::kMove_Verb:
2877 case SkPath::kClose_Verb:
2878 break;
2879 case SkPath::kLine_Verb:
2880 w += winding_line(pts, x, y);
2881 break;
2882 case SkPath::kQuad_Verb:
2883 w += winding_quad(pts, x, y);
2884 break;
2885 case SkPath::kCubic_Verb:
2886 w += winding_cubic(pts, x, y);
2887 break;
2888 case SkPath::kDone_Verb:
2889 done = true;
2890 break;
2891 }
2892 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002893
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 switch (this->getFillType()) {
2895 case SkPath::kEvenOdd_FillType:
2896 case SkPath::kInverseEvenOdd_FillType:
2897 w &= 1;
2898 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002899 default:
2900 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901 }
2902 return SkToBool(w);
2903}
2904