blob: dbdd44829184e27b292b68d9f4cc5dfd30bbde3b [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;
495 const SkPoint* savePts;
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 }
535 closedOrMoved = autoClose;
536 if (lastDirection != nextDirection) {
537 if (++corners > 4) {
538 return false; // too many direction changes
539 }
540 }
541 last = pts[-1];
542 if (lastDirection == nextDirection) {
543 break; // colinear segment
544 }
545 // Possible values for corners are 2, 3, and 4.
546 // When corners == 3, nextDirection opposes firstDirection.
547 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000548 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000549 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
550 if ((directionCycle ^ turn) != nextDirection) {
551 return false; // direction didn't follow cycle
552 }
553 break;
554 }
555 case kQuad_Verb:
556 case kCubic_Verb:
557 return false; // quadratic, cubic not allowed
558 case kMove_Verb:
559 last = *pts++;
560 closedOrMoved = true;
561 break;
562 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000563 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000564 lastDirection = nextDirection;
565 }
566 // Success if 4 corners and first point equals last
567 bool result = 4 == corners && first == last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 *ptsPtr = savePts;
569 return result;
570}
571
572bool SkPath::isRect(SkRect* rect) const {
573 SkDEBUGCODE(this->validate();)
574 int currVerb = 0;
575 const SkPoint* pts = fPathRef->points();
576 bool result = isRectContour(false, &currVerb, &pts);
caryclark@google.comf1316942011-07-26 19:54:45 +0000577 if (result && rect) {
578 *rect = getBounds();
579 }
580 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581}
582
caryclark@google.com56f233a2012-11-19 13:06:06 +0000583bool SkPath::isNestedRects(SkRect rects[2]) const {
584 SkDEBUGCODE(this->validate();)
585 int currVerb = 0;
586 const SkPoint* pts = fPathRef->points();
587 const SkPoint* first = pts;
588 if (!isRectContour(true, &currVerb, &pts)) {
589 return false;
590 }
591 const SkPoint* last = pts;
592 SkRect testRects[2];
593 if (isRectContour(false, &currVerb, &pts)) {
594 testRects[0].set(first, last - first);
595 testRects[1].set(last, pts - last);
596 if (testRects[0].contains(testRects[1])) {
597 if (rects) {
598 rects[0] = testRects[0];
599 rects[1] = testRects[1];
600 }
601 return true;
602 }
603 if (testRects[1].contains(testRects[0])) {
604 if (rects) {
605 rects[0] = testRects[1];
606 rects[1] = testRects[0];
607 }
608 return true;
609 }
610 }
611 return false;
612}
613
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614int SkPath::countPoints() const {
615 return fPathRef->countPoints();
616}
617
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000618int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 SkDEBUGCODE(this->validate();)
620
621 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000622 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = SkMin32(max, fPathRef->countPoints());
624 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
625 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626}
627
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000628SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000629 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
630 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000631 }
632 return SkPoint::Make(0, 0);
633}
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635int SkPath::countVerbs() const {
636 return fPathRef->countVerbs();
637}
638
639static inline void copy_verbs_reverse(uint8_t* inorderDst,
640 const uint8_t* reversedSrc,
641 int count) {
642 for (int i = 0; i < count; ++i) {
643 inorderDst[i] = reversedSrc[~i];
644 }
645}
646
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647int SkPath::getVerbs(uint8_t dst[], int max) const {
648 SkDEBUGCODE(this->validate();)
649
650 SkASSERT(max >= 0);
651 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000652 int count = SkMin32(max, fPathRef->countVerbs());
653 copy_verbs_reverse(dst, fPathRef->verbs(), count);
654 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000655}
656
reed@google.com294dd7b2011-10-11 11:58:32 +0000657bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 SkDEBUGCODE(this->validate();)
659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000661 if (count > 0) {
662 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000665 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000667 if (lastPt) {
668 lastPt->set(0, 0);
669 }
670 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671}
672
673void SkPath::setLastPt(SkScalar x, SkScalar y) {
674 SkDEBUGCODE(this->validate();)
675
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 if (count == 0) {
678 this->moveTo(x, y);
679 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000680 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 SkPathRef::Editor ed(&fPathRef);
682 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000683 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 }
685}
686
reed@android.comd252db02009-04-01 18:31:44 +0000687void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000689 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000692 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693}
694
reed@google.com04863fa2011-05-15 04:08:24 +0000695void SkPath::setConvexity(Convexity c) {
696 if (fConvexity != c) {
697 fConvexity = c;
698 GEN_ID_INC;
699 }
700}
701
reed@android.com8a1c16f2008-12-17 15:59:43 +0000702//////////////////////////////////////////////////////////////////////////////
703// Construction methods
704
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000705#define DIRTY_AFTER_EDIT \
706 do { \
707 fBoundsIsDirty = true; \
708 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000709 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000710 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000711 } while (0)
712
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000713#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
714 do { \
715 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000716 } while (0)
717
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718void SkPath::incReserve(U16CPU inc) {
719 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000720 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721 SkDEBUGCODE(this->validate();)
722}
723
724void SkPath::moveTo(SkScalar x, SkScalar y) {
725 SkDEBUGCODE(this->validate();)
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728
reed@google.comd335d1d2012-01-12 18:17:11 +0000729 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000730 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000734 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000735 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736}
737
738void SkPath::rMoveTo(SkScalar x, SkScalar y) {
739 SkPoint pt;
740 this->getLastPt(&pt);
741 this->moveTo(pt.fX + x, pt.fY + y);
742}
743
reed@google.comd335d1d2012-01-12 18:17:11 +0000744void SkPath::injectMoveToIfNeeded() {
745 if (fLastMoveToIndex < 0) {
746 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000748 x = y = 0;
749 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000751 x = pt.fX;
752 y = pt.fY;
753 }
754 this->moveTo(x, y);
755 }
756}
757
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758void SkPath::lineTo(SkScalar x, SkScalar y) {
759 SkDEBUGCODE(this->validate();)
760
reed@google.comd335d1d2012-01-12 18:17:11 +0000761 this->injectMoveToIfNeeded();
762
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000763 SkPathRef::Editor ed(&fPathRef);
764 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000765 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000767 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000768 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769}
770
771void SkPath::rLineTo(SkScalar x, SkScalar y) {
772 SkPoint pt;
773 this->getLastPt(&pt);
774 this->lineTo(pt.fX + x, pt.fY + y);
775}
776
777void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
778 SkDEBUGCODE(this->validate();)
779
reed@google.comd335d1d2012-01-12 18:17:11 +0000780 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000782 SkPathRef::Editor ed(&fPathRef);
783 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 pts[0].set(x1, y1);
785 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000786 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000788 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000789 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790}
791
792void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
793 SkPoint pt;
794 this->getLastPt(&pt);
795 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
796}
797
798void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
799 SkScalar x3, SkScalar y3) {
800 SkDEBUGCODE(this->validate();)
801
reed@google.comd335d1d2012-01-12 18:17:11 +0000802 this->injectMoveToIfNeeded();
803
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000804 SkPathRef::Editor ed(&fPathRef);
805 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 pts[0].set(x1, y1);
807 pts[1].set(x2, y2);
808 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000809 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000811 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000812 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813}
814
815void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
816 SkScalar x3, SkScalar y3) {
817 SkPoint pt;
818 this->getLastPt(&pt);
819 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
820 pt.fX + x3, pt.fY + y3);
821}
822
823void SkPath::close() {
824 SkDEBUGCODE(this->validate();)
825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000828 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 case kLine_Verb:
830 case kQuad_Verb:
831 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000832 case kMove_Verb: {
833 SkPathRef::Editor ed(&fPathRef);
834 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000835 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000837 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000839 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 break;
841 }
842 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000843
844 // signal that we need a moveTo to follow us (unless we're done)
845#if 0
846 if (fLastMoveToIndex >= 0) {
847 fLastMoveToIndex = ~fLastMoveToIndex;
848 }
849#else
850 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
851#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852}
853
854///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000855
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856void SkPath::addRect(const SkRect& rect, Direction dir) {
857 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
858}
859
860void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
861 SkScalar bottom, Direction dir) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000862 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
863 SkAutoDisableDirectionCheck addc(this);
864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
866
867 this->incReserve(5);
868
869 this->moveTo(left, top);
870 if (dir == kCCW_Direction) {
871 this->lineTo(left, bottom);
872 this->lineTo(right, bottom);
873 this->lineTo(right, top);
874 } else {
875 this->lineTo(right, top);
876 this->lineTo(right, bottom);
877 this->lineTo(left, bottom);
878 }
879 this->close();
880}
881
reed@google.com744faba2012-05-29 19:54:52 +0000882void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
883 SkDEBUGCODE(this->validate();)
884 if (count <= 0) {
885 return;
886 }
887
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 SkPathRef::Editor ed(&fPathRef);
889 fLastMoveToIndex = ed.pathRef()->countPoints();
890 uint8_t* vb;
891 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000892 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000893 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000894
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000895 memcpy(p, pts, count * sizeof(SkPoint));
896 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000897 if (count > 1) {
898 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
899 // be 0, the compiler will remove the test/branch entirely.
900 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000901 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000902 } else {
903 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000904 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000905 }
906 }
907 fSegmentMask |= kLine_SegmentMask;
908 }
909 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000910 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000911 }
912
913 GEN_ID_INC;
914 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000915 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000916}
917
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
919
920void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
921 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000922 SkScalar w = rect.width();
923 SkScalar halfW = SkScalarHalf(w);
924 SkScalar h = rect.height();
925 SkScalar halfH = SkScalarHalf(h);
926
927 if (halfW <= 0 || halfH <= 0) {
928 return;
929 }
930
reed@google.comabf15c12011-01-18 20:35:51 +0000931 bool skip_hori = rx >= halfW;
932 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000933
934 if (skip_hori && skip_vert) {
935 this->addOval(rect, dir);
936 return;
937 }
reed@google.comabf15c12011-01-18 20:35:51 +0000938
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000939 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
940
reed@google.comabf15c12011-01-18 20:35:51 +0000941 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000942 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000943
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 if (skip_hori) {
945 rx = halfW;
946 } else if (skip_vert) {
947 ry = halfH;
948 }
949
950 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
951 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
952
953 this->incReserve(17);
954 this->moveTo(rect.fRight - rx, rect.fTop);
955 if (dir == kCCW_Direction) {
956 if (!skip_hori) {
957 this->lineTo(rect.fLeft + rx, rect.fTop); // top
958 }
959 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
960 rect.fLeft, rect.fTop + ry - sy,
961 rect.fLeft, rect.fTop + ry); // top-left
962 if (!skip_vert) {
963 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
964 }
965 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
966 rect.fLeft + rx - sx, rect.fBottom,
967 rect.fLeft + rx, rect.fBottom); // bot-left
968 if (!skip_hori) {
969 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
970 }
971 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
972 rect.fRight, rect.fBottom - ry + sy,
973 rect.fRight, rect.fBottom - ry); // bot-right
974 if (!skip_vert) {
975 this->lineTo(rect.fRight, rect.fTop + ry);
976 }
977 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
978 rect.fRight - rx + sx, rect.fTop,
979 rect.fRight - rx, rect.fTop); // top-right
980 } else {
981 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
982 rect.fRight, rect.fTop + ry - sy,
983 rect.fRight, rect.fTop + ry); // top-right
984 if (!skip_vert) {
985 this->lineTo(rect.fRight, rect.fBottom - ry);
986 }
987 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
988 rect.fRight - rx + sx, rect.fBottom,
989 rect.fRight - rx, rect.fBottom); // bot-right
990 if (!skip_hori) {
991 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
992 }
993 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
994 rect.fLeft, rect.fBottom - ry + sy,
995 rect.fLeft, rect.fBottom - ry); // bot-left
996 if (!skip_vert) {
997 this->lineTo(rect.fLeft, rect.fTop + ry); // left
998 }
999 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1000 rect.fLeft + rx - sx, rect.fTop,
1001 rect.fLeft + rx, rect.fTop); // top-left
1002 if (!skip_hori) {
1003 this->lineTo(rect.fRight - rx, rect.fTop); // top
1004 }
1005 }
1006 this->close();
1007}
1008
1009static void add_corner_arc(SkPath* path, const SkRect& rect,
1010 SkScalar rx, SkScalar ry, int startAngle,
1011 SkPath::Direction dir, bool forceMoveTo) {
1012 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
1013 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +00001014
reed@android.com8a1c16f2008-12-17 15:59:43 +00001015 SkRect r;
1016 r.set(-rx, -ry, rx, ry);
1017
1018 switch (startAngle) {
1019 case 0:
1020 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1021 break;
1022 case 90:
1023 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1024 break;
1025 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1026 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001027 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028 }
reed@google.comabf15c12011-01-18 20:35:51 +00001029
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 SkScalar start = SkIntToScalar(startAngle);
1031 SkScalar sweep = SkIntToScalar(90);
1032 if (SkPath::kCCW_Direction == dir) {
1033 start += sweep;
1034 sweep = -sweep;
1035 }
reed@google.comabf15c12011-01-18 20:35:51 +00001036
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 path->arcTo(r, start, sweep, forceMoveTo);
1038}
1039
1040void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
1041 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +00001042 // abort before we invoke SkAutoPathBoundsUpdate()
1043 if (rect.isEmpty()) {
1044 return;
1045 }
1046
reed@android.com8a1c16f2008-12-17 15:59:43 +00001047 SkAutoPathBoundsUpdate apbu(this, rect);
1048
1049 if (kCW_Direction == dir) {
1050 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1051 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1052 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1053 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1054 } else {
1055 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1056 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1057 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1058 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1059 }
1060 this->close();
1061}
1062
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001063bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001064 int count = fPathRef->countVerbs();
1065 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1066 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001067 if (*verbs == kLine_Verb ||
1068 *verbs == kQuad_Verb ||
1069 *verbs == kCubic_Verb) {
1070 return false;
1071 }
1072 ++verbs;
1073 }
1074 return true;
1075}
1076
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001078 /* If addOval() is called after previous moveTo(),
1079 this path is still marked as an oval. This is used to
1080 fit into WebKit's calling sequences.
1081 We can't simply check isEmpty() in this case, as additional
1082 moveTo() would mark the path non empty.
1083 */
1084 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001085 if (fIsOval) {
1086 fDirection = dir;
1087 } else {
1088 fDirection = kUnknown_Direction;
1089 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001090
1091 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001092 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001093
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 SkAutoPathBoundsUpdate apbu(this, oval);
1095
1096 SkScalar cx = oval.centerX();
1097 SkScalar cy = oval.centerY();
1098 SkScalar rx = SkScalarHalf(oval.width());
1099 SkScalar ry = SkScalarHalf(oval.height());
1100#if 0 // these seem faster than using quads (1/2 the number of edges)
1101 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1102 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1103
1104 this->incReserve(13);
1105 this->moveTo(cx + rx, cy);
1106 if (dir == kCCW_Direction) {
1107 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1108 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1109 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1110 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1111 } else {
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 }
1117#else
1118 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1119 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1120 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1121 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1122
1123 /*
1124 To handle imprecision in computing the center and radii, we revert to
1125 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1126 to ensure that we don't exceed the oval's bounds *ever*, since we want
1127 to use oval for our fast-bounds, rather than have to recompute it.
1128 */
1129 const SkScalar L = oval.fLeft; // cx - rx
1130 const SkScalar T = oval.fTop; // cy - ry
1131 const SkScalar R = oval.fRight; // cx + rx
1132 const SkScalar B = oval.fBottom; // cy + ry
1133
1134 this->incReserve(17); // 8 quads + close
1135 this->moveTo(R, cy);
1136 if (dir == kCCW_Direction) {
1137 this->quadTo( R, cy - sy, cx + mx, cy - my);
1138 this->quadTo(cx + sx, T, cx , T);
1139 this->quadTo(cx - sx, T, cx - mx, cy - my);
1140 this->quadTo( L, cy - sy, L, cy );
1141 this->quadTo( L, cy + sy, cx - mx, cy + my);
1142 this->quadTo(cx - sx, B, cx , B);
1143 this->quadTo(cx + sx, B, cx + mx, cy + my);
1144 this->quadTo( R, cy + sy, R, cy );
1145 } else {
1146 this->quadTo( R, 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( L, cy + sy, L, cy );
1150 this->quadTo( L, cy - sy, cx - mx, cy - my);
1151 this->quadTo(cx - sx, T, cx , T);
1152 this->quadTo(cx + sx, T, cx + mx, cy - my);
1153 this->quadTo( R, cy - sy, R, cy );
1154 }
1155#endif
1156 this->close();
1157}
1158
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001159bool SkPath::isOval(SkRect* rect) const {
1160 if (fIsOval && rect) {
1161 *rect = getBounds();
1162 }
1163
1164 return fIsOval;
1165}
1166
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1168 if (r > 0) {
1169 SkRect rect;
1170 rect.set(x - r, y - r, x + r, y + r);
1171 this->addOval(rect, dir);
1172 }
1173}
1174
1175#include "SkGeometry.h"
1176
1177static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1178 SkScalar sweepAngle,
1179 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001180
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001181 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001182 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1183 // Chrome uses this path to move into and out of ovals. If not
1184 // treated as a special case the moves can distort the oval's
1185 // bounding box (and break the circle special case).
1186 pts[0].set(oval.fRight, oval.centerY());
1187 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001188 } else if (0 == oval.width() && 0 == oval.height()) {
1189 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001190 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001191 // a rect.
1192 // TODO: optimizing the case where only one of width or height is zero
1193 // should also be considered. This case, however, doesn't seem to be
1194 // as common as the single point case.
1195 pts[0].set(oval.fRight, oval.fTop);
1196 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001197 }
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 SkVector start, stop;
1200
1201 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1202 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1203 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001204
1205 /* If the sweep angle is nearly (but less than) 360, then due to precision
1206 loss in radians-conversion and/or sin/cos, we may end up with coincident
1207 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1208 of drawing a nearly complete circle (good).
1209 e.g. canvas.drawArc(0, 359.99, ...)
1210 -vs- canvas.drawArc(0, 359.9, ...)
1211 We try to detect this edge case, and tweak the stop vector
1212 */
1213 if (start == stop) {
1214 SkScalar sw = SkScalarAbs(sweepAngle);
1215 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1216 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1217 // make a guess at a tiny angle (in radians) to tweak by
1218 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1219 // not sure how much will be enough, so we use a loop
1220 do {
1221 stopRad -= deltaRad;
1222 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1223 } while (start == stop);
1224 }
1225 }
1226
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1230 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 return SkBuildQuadArc(start, stop,
1233 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1234 &matrix, pts);
1235}
1236
1237void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1238 bool forceMoveTo) {
1239 if (oval.width() < 0 || oval.height() < 0) {
1240 return;
1241 }
1242
1243 SkPoint pts[kSkBuildQuadArcStorage];
1244 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1245 SkASSERT((count & 1) == 1);
1246
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001247 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 forceMoveTo = true;
1249 }
1250 this->incReserve(count);
1251 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1252 for (int i = 1; i < count; i += 2) {
1253 this->quadTo(pts[i], pts[i+1]);
1254 }
1255}
1256
1257void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1258 SkScalar sweepAngle) {
1259 if (oval.isEmpty() || 0 == sweepAngle) {
1260 return;
1261 }
1262
1263 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1264
1265 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1266 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1267 return;
1268 }
1269
1270 SkPoint pts[kSkBuildQuadArcStorage];
1271 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1272
1273 this->incReserve(count);
1274 this->moveTo(pts[0]);
1275 for (int i = 1; i < count; i += 2) {
1276 this->quadTo(pts[i], pts[i+1]);
1277 }
1278}
1279
1280/*
1281 Need to handle the case when the angle is sharp, and our computed end-points
1282 for the arc go behind pt1 and/or p2...
1283*/
1284void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1285 SkScalar radius) {
1286 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 // need to know our prev pt so we can construct tangent vectors
1289 {
1290 SkPoint start;
1291 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001292 // Handle degenerate cases by adding a line to the first point and
1293 // bailing out.
1294 if ((x1 == start.fX && y1 == start.fY) ||
1295 (x1 == x2 && y1 == y2) ||
1296 radius == 0) {
1297 this->lineTo(x1, y1);
1298 return;
1299 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 before.setNormalize(x1 - start.fX, y1 - start.fY);
1301 after.setNormalize(x2 - x1, y2 - y1);
1302 }
reed@google.comabf15c12011-01-18 20:35:51 +00001303
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 SkScalar cosh = SkPoint::DotProduct(before, after);
1305 SkScalar sinh = SkPoint::CrossProduct(before, after);
1306
1307 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001308 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 return;
1310 }
reed@google.comabf15c12011-01-18 20:35:51 +00001311
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1313 if (dist < 0) {
1314 dist = -dist;
1315 }
1316
1317 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1318 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1319 SkRotationDirection arcDir;
1320
1321 // now turn before/after into normals
1322 if (sinh > 0) {
1323 before.rotateCCW();
1324 after.rotateCCW();
1325 arcDir = kCW_SkRotationDirection;
1326 } else {
1327 before.rotateCW();
1328 after.rotateCW();
1329 arcDir = kCCW_SkRotationDirection;
1330 }
1331
1332 SkMatrix matrix;
1333 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001334
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 matrix.setScale(radius, radius);
1336 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1337 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001338
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001340
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 this->incReserve(count);
1342 // [xx,yy] == pts[0]
1343 this->lineTo(xx, yy);
1344 for (int i = 1; i < count; i += 2) {
1345 this->quadTo(pts[i], pts[i+1]);
1346 }
1347}
1348
1349///////////////////////////////////////////////////////////////////////////////
1350
1351void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1352 SkMatrix matrix;
1353
1354 matrix.setTranslate(dx, dy);
1355 this->addPath(path, matrix);
1356}
1357
1358void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001359 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001361 fIsOval = false;
1362
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001363 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 SkPoint pts[4];
1365 Verb verb;
1366
1367 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1368
1369 while ((verb = iter.next(pts)) != kDone_Verb) {
1370 switch (verb) {
1371 case kMove_Verb:
1372 proc(matrix, &pts[0], &pts[0], 1);
1373 this->moveTo(pts[0]);
1374 break;
1375 case kLine_Verb:
1376 proc(matrix, &pts[1], &pts[1], 1);
1377 this->lineTo(pts[1]);
1378 break;
1379 case kQuad_Verb:
1380 proc(matrix, &pts[1], &pts[1], 2);
1381 this->quadTo(pts[1], pts[2]);
1382 break;
1383 case kCubic_Verb:
1384 proc(matrix, &pts[1], &pts[1], 3);
1385 this->cubicTo(pts[1], pts[2], pts[3]);
1386 break;
1387 case kClose_Verb:
1388 this->close();
1389 break;
1390 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001391 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001392 }
1393 }
1394}
1395
1396///////////////////////////////////////////////////////////////////////////////
1397
1398static const uint8_t gPtsInVerb[] = {
1399 1, // kMove
1400 1, // kLine
1401 2, // kQuad
1402 3, // kCubic
1403 0, // kClose
1404 0 // kDone
1405};
1406
1407// ignore the initial moveto, and stop when the 1st contour ends
1408void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001409 int i, vcount = path.fPathRef->countVerbs();
1410 // exit early if the path is empty, or just has a moveTo.
1411 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 return;
1413 }
1414
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001415 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001417 fIsOval = false;
1418
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001419 const uint8_t* verbs = path.fPathRef->verbs();
1420 // skip the initial moveTo
1421 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001423 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001425 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 case kLine_Verb:
1427 this->lineTo(pts[0].fX, pts[0].fY);
1428 break;
1429 case kQuad_Verb:
1430 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1431 break;
1432 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001433 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 +00001434 break;
1435 case kClose_Verb:
1436 return;
1437 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001438 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 }
1440}
1441
1442// ignore the last point of the 1st contour
1443void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001444 int i, vcount = path.fPathRef->countVerbs();
1445 // exit early if the path is empty, or just has a moveTo.
1446 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 return;
1448 }
1449
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001450 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001452 fIsOval = false;
1453
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 const uint8_t* verbs = path.fPathRef->verbs();
1455 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001457 SkASSERT(verbs[~0] == kMove_Verb);
1458 for (i = 1; i < vcount; ++i) {
1459 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 if (n == 0) {
1461 break;
1462 }
1463 pts += n;
1464 }
1465
1466 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001467 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 case kLine_Verb:
1469 this->lineTo(pts[-1].fX, pts[-1].fY);
1470 break;
1471 case kQuad_Verb:
1472 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1473 break;
1474 case kCubic_Verb:
1475 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1476 pts[-3].fX, pts[-3].fY);
1477 break;
1478 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001479 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 break;
1481 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001482 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 }
1484}
1485
reed@google.com63d73742012-01-10 15:33:12 +00001486void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001487 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001488
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 const SkPoint* pts = src.fPathRef->pointsEnd();
1490 // we will iterator through src's verbs backwards
1491 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1492 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001493
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001494 fIsOval = false;
1495
reed@google.com63d73742012-01-10 15:33:12 +00001496 bool needMove = true;
1497 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 while (verbs < verbsEnd) {
1499 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001500 int n = gPtsInVerb[v];
1501
1502 if (needMove) {
1503 --pts;
1504 this->moveTo(pts->fX, pts->fY);
1505 needMove = false;
1506 }
1507 pts -= n;
1508 switch (v) {
1509 case kMove_Verb:
1510 if (needClose) {
1511 this->close();
1512 needClose = false;
1513 }
1514 needMove = true;
1515 pts += 1; // so we see the point in "if (needMove)" above
1516 break;
1517 case kLine_Verb:
1518 this->lineTo(pts[0]);
1519 break;
1520 case kQuad_Verb:
1521 this->quadTo(pts[1], pts[0]);
1522 break;
1523 case kCubic_Verb:
1524 this->cubicTo(pts[2], pts[1], pts[0]);
1525 break;
1526 case kClose_Verb:
1527 needClose = true;
1528 break;
1529 default:
1530 SkASSERT(!"unexpected verb");
1531 }
1532 }
1533}
1534
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535///////////////////////////////////////////////////////////////////////////////
1536
1537void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1538 SkMatrix matrix;
1539
1540 matrix.setTranslate(dx, dy);
1541 this->transform(matrix, dst);
1542}
1543
1544#include "SkGeometry.h"
1545
1546static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1547 int level = 2) {
1548 if (--level >= 0) {
1549 SkPoint tmp[5];
1550
1551 SkChopQuadAtHalf(pts, tmp);
1552 subdivide_quad_to(path, &tmp[0], level);
1553 subdivide_quad_to(path, &tmp[2], level);
1554 } else {
1555 path->quadTo(pts[1], pts[2]);
1556 }
1557}
1558
1559static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1560 int level = 2) {
1561 if (--level >= 0) {
1562 SkPoint tmp[7];
1563
1564 SkChopCubicAtHalf(pts, tmp);
1565 subdivide_cubic_to(path, &tmp[0], level);
1566 subdivide_cubic_to(path, &tmp[3], level);
1567 } else {
1568 path->cubicTo(pts[1], pts[2], pts[3]);
1569 }
1570}
1571
1572void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1573 SkDEBUGCODE(this->validate();)
1574 if (dst == NULL) {
1575 dst = (SkPath*)this;
1576 }
1577
tomhudson@google.com8d430182011-06-06 19:11:19 +00001578 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579 SkPath tmp;
1580 tmp.fFillType = fFillType;
1581
1582 SkPath::Iter iter(*this, false);
1583 SkPoint pts[4];
1584 SkPath::Verb verb;
1585
reed@google.com4a3b7142012-05-16 17:16:46 +00001586 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 switch (verb) {
1588 case kMove_Verb:
1589 tmp.moveTo(pts[0]);
1590 break;
1591 case kLine_Verb:
1592 tmp.lineTo(pts[1]);
1593 break;
1594 case kQuad_Verb:
1595 subdivide_quad_to(&tmp, pts);
1596 break;
1597 case kCubic_Verb:
1598 subdivide_cubic_to(&tmp, pts);
1599 break;
1600 case kClose_Verb:
1601 tmp.close();
1602 break;
1603 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001604 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 break;
1606 }
1607 }
1608
1609 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001610 SkPathRef::Editor ed(&dst->fPathRef);
1611 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001612 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001614 /*
1615 * If we're not in perspective, we can transform all of the points at
1616 * once.
1617 *
1618 * Here we also want to optimize bounds, by noting if the bounds are
1619 * already known, and if so, we just transform those as well and mark
1620 * them as "known", rather than force the transformed path to have to
1621 * recompute them.
1622 *
1623 * Special gotchas if the path is effectively empty (<= 1 point) or
1624 * if it is non-finite. In those cases bounds need to stay empty,
1625 * regardless of the matrix.
1626 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001627 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001628 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001629 if (fIsFinite) {
1630 matrix.mapRect(&dst->fBounds, fBounds);
1631 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1632 dst->fBounds.setEmpty();
1633 }
1634 } else {
1635 dst->fIsFinite = false;
1636 dst->fBounds.setEmpty();
1637 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001639 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001640 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 }
1642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001643 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1644
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001647 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001648 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001650
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001651 if (kUnknown_Direction == fDirection) {
1652 dst->fDirection = kUnknown_Direction;
1653 } else {
1654 SkScalar det2x2 =
1655 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1656 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1657 if (det2x2 < 0) {
1658 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1659 } else if (det2x2 > 0) {
1660 dst->fDirection = fDirection;
1661 } else {
1662 dst->fDirection = kUnknown_Direction;
1663 }
1664 }
1665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001666 // It's an oval only if it stays a rect.
1667 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001668
reed@android.com8a1c16f2008-12-17 15:59:43 +00001669 SkDEBUGCODE(dst->validate();)
1670 }
1671}
1672
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673///////////////////////////////////////////////////////////////////////////////
1674///////////////////////////////////////////////////////////////////////////////
1675
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001676enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001677 kEmptyContour_SegmentState, // The current contour is empty. We may be
1678 // starting processing or we may have just
1679 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001680 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1681 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1682 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683};
1684
1685SkPath::Iter::Iter() {
1686#ifdef SK_DEBUG
1687 fPts = NULL;
1688 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001689 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001690 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691#endif
1692 // need to init enough to make next() harmlessly return kDone_Verb
1693 fVerbs = NULL;
1694 fVerbStop = NULL;
1695 fNeedClose = false;
1696}
1697
1698SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1699 this->setPath(path, forceClose);
1700}
1701
1702void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001703 fPts = path.fPathRef->points();
1704 fVerbs = path.fPathRef->verbs();
1705 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001706 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001707 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 fForceClose = SkToU8(forceClose);
1709 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001710 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711}
1712
1713bool SkPath::Iter::isClosedContour() const {
1714 if (fVerbs == NULL || fVerbs == fVerbStop) {
1715 return false;
1716 }
1717 if (fForceClose) {
1718 return true;
1719 }
1720
1721 const uint8_t* verbs = fVerbs;
1722 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001724 if (kMove_Verb == *(verbs - 1)) {
1725 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726 }
1727
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001728 while (verbs > stop) {
1729 // verbs points one beyond the current verb, decrement first.
1730 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 if (kMove_Verb == v) {
1732 break;
1733 }
1734 if (kClose_Verb == v) {
1735 return true;
1736 }
1737 }
1738 return false;
1739}
1740
1741SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001742 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001744 // A special case: if both points are NaN, SkPoint::operation== returns
1745 // false, but the iterator expects that they are treated as the same.
1746 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001747 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1748 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001749 return kClose_Verb;
1750 }
1751
reed@google.com9e25dbf2012-05-15 17:05:38 +00001752 pts[0] = fLastPt;
1753 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754 fLastPt = fMoveTo;
1755 fCloseLine = true;
1756 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001757 } else {
1758 pts[0] = fMoveTo;
1759 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761}
1762
reed@google.com9e25dbf2012-05-15 17:05:38 +00001763const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001764 if (fSegmentState == kAfterMove_SegmentState) {
1765 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001767 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1770 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001771 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773}
1774
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775void SkPath::Iter::consumeDegenerateSegments() {
1776 // We need to step over anything that will not move the current draw point
1777 // forward before the next move is seen
1778 const uint8_t* lastMoveVerb = 0;
1779 const SkPoint* lastMovePt = 0;
1780 SkPoint lastPt = fLastPt;
1781 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 switch (verb) {
1784 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001785 // Keep a record of this most recent move
1786 lastMoveVerb = fVerbs;
1787 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001788 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001790 fPts++;
1791 break;
1792
1793 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001794 // A close when we are in a segment is always valid except when it
1795 // follows a move which follows a segment.
1796 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001797 return;
1798 }
1799 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001800 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001801 break;
1802
1803 case kLine_Verb:
1804 if (!IsLineDegenerate(lastPt, fPts[0])) {
1805 if (lastMoveVerb) {
1806 fVerbs = lastMoveVerb;
1807 fPts = lastMovePt;
1808 return;
1809 }
1810 return;
1811 }
1812 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 fPts++;
1815 break;
1816
1817 case kQuad_Verb:
1818 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1819 if (lastMoveVerb) {
1820 fVerbs = lastMoveVerb;
1821 fPts = lastMovePt;
1822 return;
1823 }
1824 return;
1825 }
1826 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fPts += 2;
1829 break;
1830
1831 case kCubic_Verb:
1832 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1833 if (lastMoveVerb) {
1834 fVerbs = lastMoveVerb;
1835 fPts = lastMovePt;
1836 return;
1837 }
1838 return;
1839 }
1840 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001841 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 fPts += 3;
1843 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001844
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001846 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 }
1848 }
1849}
1850
reed@google.com4a3b7142012-05-16 17:16:46 +00001851SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001852 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 // Close the curve if requested and if there is some curve to close
1856 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001857 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 return kLine_Verb;
1859 }
1860 fNeedClose = false;
1861 return kClose_Verb;
1862 }
1863 return kDone_Verb;
1864 }
1865
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001866 // fVerbs is one beyond the current verb, decrement first
1867 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001868 const SkPoint* SK_RESTRICT srcPts = fPts;
1869 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870
1871 switch (verb) {
1872 case kMove_Verb:
1873 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 verb = this->autoClose(pts);
1876 if (verb == kClose_Verb) {
1877 fNeedClose = false;
1878 }
1879 return (Verb)verb;
1880 }
1881 if (fVerbs == fVerbStop) { // might be a trailing moveto
1882 return kDone_Verb;
1883 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001884 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001885 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001887 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889 fNeedClose = fForceClose;
1890 break;
1891 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 pts[0] = this->cons_moveTo();
1893 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 fLastPt = srcPts[0];
1895 fCloseLine = false;
1896 srcPts += 1;
1897 break;
1898 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899 pts[0] = this->cons_moveTo();
1900 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 fLastPt = srcPts[1];
1902 srcPts += 2;
1903 break;
1904 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001905 pts[0] = this->cons_moveTo();
1906 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 fLastPt = srcPts[2];
1908 srcPts += 3;
1909 break;
1910 case kClose_Verb:
1911 verb = this->autoClose(pts);
1912 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001913 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 } else {
1915 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001916 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 break;
1920 }
1921 fPts = srcPts;
1922 return (Verb)verb;
1923}
1924
1925///////////////////////////////////////////////////////////////////////////////
1926
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001927SkPath::RawIter::RawIter() {
1928#ifdef SK_DEBUG
1929 fPts = NULL;
1930 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1931#endif
1932 // need to init enough to make next() harmlessly return kDone_Verb
1933 fVerbs = NULL;
1934 fVerbStop = NULL;
1935}
1936
1937SkPath::RawIter::RawIter(const SkPath& path) {
1938 this->setPath(path);
1939}
1940
1941void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 fPts = path.fPathRef->points();
1943 fVerbs = path.fPathRef->verbs();
1944 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001945 fMoveTo.fX = fMoveTo.fY = 0;
1946 fLastPt.fX = fLastPt.fY = 0;
1947}
1948
1949SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001950 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001951 if (fVerbs == fVerbStop) {
1952 return kDone_Verb;
1953 }
1954
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 // fVerbs points one beyond next verb so decrement first.
1956 unsigned verb = *(--fVerbs);
1957 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001958
1959 switch (verb) {
1960 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001961 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001962 fMoveTo = srcPts[0];
1963 fLastPt = fMoveTo;
1964 srcPts += 1;
1965 break;
1966 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001967 pts[0] = fLastPt;
1968 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001969 fLastPt = srcPts[0];
1970 srcPts += 1;
1971 break;
1972 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001973 pts[0] = fLastPt;
1974 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001975 fLastPt = srcPts[1];
1976 srcPts += 2;
1977 break;
1978 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001979 pts[0] = fLastPt;
1980 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001981 fLastPt = srcPts[2];
1982 srcPts += 3;
1983 break;
1984 case kClose_Verb:
1985 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001986 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001987 break;
1988 }
1989 fPts = srcPts;
1990 return (Verb)verb;
1991}
1992
1993///////////////////////////////////////////////////////////////////////////////
1994
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001996 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997*/
1998
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001999uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 SkDEBUGCODE(this->validate();)
2001
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002002 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002003 const int byteCount = sizeof(int32_t)
2004#if NEW_PICTURE_FORMAT
2005 + fPathRef->writeSize()
2006#else
2007 + 2 * sizeof(int32_t)
2008 + sizeof(SkPoint) * fPathRef->countPoints()
2009 + sizeof(uint8_t) * fPathRef->countVerbs()
2010#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002012 return SkAlign4(byteCount);
2013 }
2014
2015 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002016#if !NEW_PICTURE_FORMAT
2017 buffer.write32(fPathRef->countPoints());
2018 buffer.write32(fPathRef->countVerbs());
2019#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002020
2021 // Call getBounds() to ensure (as a side-effect) that fBounds
2022 // and fIsFinite are computed.
2023 const SkRect& bounds = this->getBounds();
2024 SkASSERT(!fBoundsIsDirty);
2025
2026 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2027 ((fIsOval & 1) << kIsOval_SerializationShift) |
2028 (fConvexity << kConvexity_SerializationShift) |
2029 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002030 (fSegmentMask << kSegmentMask_SerializationShift) |
2031 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002032
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002033 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002034
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002035 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002036
2037 buffer.write(&bounds, sizeof(bounds));
2038
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002039 buffer.padToAlign4();
2040 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041}
2042
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043uint32_t SkPath::readFromMemory(const void* storage) {
2044 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002045#if !NEW_PICTURE_FORMAT
2046 int32_t pcount = buffer.readS32();
2047 int32_t vcount = buffer.readS32();
2048#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002049
reed@google.com98b11f12011-09-21 18:40:27 +00002050 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002051 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2052 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2053 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2054 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002055 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2056 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002057
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002058#if NEW_PICTURE_FORMAT
2059 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2060#else
2061 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2062#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063
2064 buffer.read(&fBounds, sizeof(fBounds));
2065 fBoundsIsDirty = false;
2066
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002067 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002068
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002069 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070
2071 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002072 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073}
2074
2075///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077void SkPath::dump(bool forceClose, const char title[]) const {
2078 Iter iter(*this, forceClose);
2079 SkPoint pts[4];
2080 Verb verb;
2081
2082 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2083 title ? title : "");
2084
reed@google.com4a3b7142012-05-16 17:16:46 +00002085 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086 switch (verb) {
2087 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 SkDebugf(" path: moveTo [%g %g]\n",
2089 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090 break;
2091 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002092 SkDebugf(" path: lineTo [%g %g]\n",
2093 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 break;
2095 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2097 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2098 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 break;
2100 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2102 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2103 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2104 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 break;
2106 case kClose_Verb:
2107 SkDebugf(" path: close\n");
2108 break;
2109 default:
2110 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2111 verb = kDone_Verb; // stop the loop
2112 break;
2113 }
2114 }
2115 SkDebugf("path: done %s\n", title ? title : "");
2116}
2117
reed@android.come522ca52009-11-23 20:10:41 +00002118void SkPath::dump() const {
2119 this->dump(false);
2120}
2121
2122#ifdef SK_DEBUG
2123void SkPath::validate() const {
2124 SkASSERT(this != NULL);
2125 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002126
djsollen@google.com077348c2012-10-22 20:23:32 +00002127#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002128 if (!fBoundsIsDirty) {
2129 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002130
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002131 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002132 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002133
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002134 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002135 // if we're empty, fBounds may be empty but translated, so we can't
2136 // necessarily compare to bounds directly
2137 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2138 // be [2, 2, 2, 2]
2139 SkASSERT(bounds.isEmpty());
2140 SkASSERT(fBounds.isEmpty());
2141 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002142 if (bounds.isEmpty()) {
2143 SkASSERT(fBounds.isEmpty());
2144 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002145 if (!fBounds.isEmpty()) {
2146 SkASSERT(fBounds.contains(bounds));
2147 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002148 }
reed@android.come522ca52009-11-23 20:10:41 +00002149 }
2150 }
reed@google.com10296cc2011-09-21 12:29:05 +00002151
2152 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002153 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2154 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2155 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002156 case kLine_Verb:
2157 mask |= kLine_SegmentMask;
2158 break;
2159 case kQuad_Verb:
2160 mask |= kQuad_SegmentMask;
2161 break;
2162 case kCubic_Verb:
2163 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002164 case kMove_Verb: // these verbs aren't included in the segment mask.
2165 case kClose_Verb:
2166 break;
2167 case kDone_Verb:
2168 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2169 break;
2170 default:
2171 SkDEBUGFAIL("Unknown Verb");
2172 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002173 }
2174 }
2175 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002176#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002177}
djsollen@google.com077348c2012-10-22 20:23:32 +00002178#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002179
reed@google.com04863fa2011-05-15 04:08:24 +00002180///////////////////////////////////////////////////////////////////////////////
2181
reed@google.com0b7b9822011-05-16 12:29:27 +00002182static int sign(SkScalar x) { return x < 0; }
2183#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002184
reed@google.com04863fa2011-05-15 04:08:24 +00002185static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002186 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002187}
2188
2189// only valid for a single contour
2190struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002191 Convexicator()
2192 : fPtCount(0)
2193 , fConvexity(SkPath::kConvex_Convexity)
2194 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002195 fSign = 0;
2196 // warnings
2197 fCurrPt.set(0, 0);
2198 fVec0.set(0, 0);
2199 fVec1.set(0, 0);
2200 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002201
2202 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002203 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002204 }
2205
2206 SkPath::Convexity getConvexity() const { return fConvexity; }
2207
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002208 /** The direction returned is only valid if the path is determined convex */
2209 SkPath::Direction getDirection() const { return fDirection; }
2210
reed@google.com04863fa2011-05-15 04:08:24 +00002211 void addPt(const SkPoint& pt) {
2212 if (SkPath::kConcave_Convexity == fConvexity) {
2213 return;
2214 }
2215
2216 if (0 == fPtCount) {
2217 fCurrPt = pt;
2218 ++fPtCount;
2219 } else {
2220 SkVector vec = pt - fCurrPt;
2221 if (vec.fX || vec.fY) {
2222 fCurrPt = pt;
2223 if (++fPtCount == 2) {
2224 fFirstVec = fVec1 = vec;
2225 } else {
2226 SkASSERT(fPtCount > 2);
2227 this->addVec(vec);
2228 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002229
reed@google.com85b6e392011-05-15 20:25:17 +00002230 int sx = sign(vec.fX);
2231 int sy = sign(vec.fY);
2232 fDx += (sx != fSx);
2233 fDy += (sy != fSy);
2234 fSx = sx;
2235 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002236
reed@google.com85b6e392011-05-15 20:25:17 +00002237 if (fDx > 3 || fDy > 3) {
2238 fConvexity = SkPath::kConcave_Convexity;
2239 }
reed@google.com04863fa2011-05-15 04:08:24 +00002240 }
2241 }
2242 }
2243
2244 void close() {
2245 if (fPtCount > 2) {
2246 this->addVec(fFirstVec);
2247 }
2248 }
2249
2250private:
2251 void addVec(const SkVector& vec) {
2252 SkASSERT(vec.fX || vec.fY);
2253 fVec0 = fVec1;
2254 fVec1 = vec;
2255 int sign = CrossProductSign(fVec0, fVec1);
2256 if (0 == fSign) {
2257 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002258 if (1 == sign) {
2259 fDirection = SkPath::kCW_Direction;
2260 } else if (-1 == sign) {
2261 fDirection = SkPath::kCCW_Direction;
2262 }
reed@google.com04863fa2011-05-15 04:08:24 +00002263 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002264 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002265 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002266 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 }
2268 }
2269 }
2270
2271 SkPoint fCurrPt;
2272 SkVector fVec0, fVec1, fFirstVec;
2273 int fPtCount; // non-degenerate points
2274 int fSign;
2275 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002276 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002277 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002278};
2279
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280SkPath::Convexity SkPath::internalGetConvexity() const {
2281 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002282 SkPoint pts[4];
2283 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002284 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002285
2286 int contourCount = 0;
2287 int count;
2288 Convexicator state;
2289
2290 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2291 switch (verb) {
2292 case kMove_Verb:
2293 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002294 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 return kConcave_Convexity;
2296 }
2297 pts[1] = pts[0];
2298 count = 1;
2299 break;
2300 case kLine_Verb: count = 1; break;
2301 case kQuad_Verb: count = 2; break;
2302 case kCubic_Verb: count = 3; break;
2303 case kClose_Verb:
2304 state.close();
2305 count = 0;
2306 break;
2307 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002308 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002309 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002310 return kConcave_Convexity;
2311 }
2312
2313 for (int i = 1; i <= count; i++) {
2314 state.addPt(pts[i]);
2315 }
2316 // early exit
2317 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002318 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002319 return kConcave_Convexity;
2320 }
2321 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002322 fConvexity = state.getConvexity();
2323 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2324 fDirection = state.getDirection();
2325 }
2326 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002327}
reed@google.com69a99432012-01-10 18:00:10 +00002328
2329///////////////////////////////////////////////////////////////////////////////
2330
2331class ContourIter {
2332public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002333 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002334
2335 bool done() const { return fDone; }
2336 // if !done() then these may be called
2337 int count() const { return fCurrPtCount; }
2338 const SkPoint* pts() const { return fCurrPt; }
2339 void next();
2340
2341private:
2342 int fCurrPtCount;
2343 const SkPoint* fCurrPt;
2344 const uint8_t* fCurrVerb;
2345 const uint8_t* fStopVerbs;
2346 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002347 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002348};
2349
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002350ContourIter::ContourIter(const SkPathRef& pathRef) {
2351 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002352 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002353 fCurrPt = pathRef.points();
2354 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002355 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002356 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002357 this->next();
2358}
2359
2360void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002361 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002362 fDone = true;
2363 }
2364 if (fDone) {
2365 return;
2366 }
2367
2368 // skip pts of prev contour
2369 fCurrPt += fCurrPtCount;
2370
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002371 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002372 int ptCount = 1; // moveTo
2373 const uint8_t* verbs = fCurrVerb;
2374
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002375 for (--verbs; verbs > fStopVerbs; --verbs) {
2376 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002377 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002378 goto CONTOUR_END;
2379 case SkPath::kLine_Verb:
2380 ptCount += 1;
2381 break;
2382 case SkPath::kQuad_Verb:
2383 ptCount += 2;
2384 break;
2385 case SkPath::kCubic_Verb:
2386 ptCount += 3;
2387 break;
2388 default: // kClose_Verb, just keep going
2389 break;
2390 }
2391 }
2392CONTOUR_END:
2393 fCurrPtCount = ptCount;
2394 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002395 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002396}
2397
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002398// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002399static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002400 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2401 // We may get 0 when the above subtracts underflow. We expect this to be
2402 // very rare and lazily promote to double.
2403 if (0 == cross) {
2404 double p0x = SkScalarToDouble(p0.fX);
2405 double p0y = SkScalarToDouble(p0.fY);
2406
2407 double p1x = SkScalarToDouble(p1.fX);
2408 double p1y = SkScalarToDouble(p1.fY);
2409
2410 double p2x = SkScalarToDouble(p2.fX);
2411 double p2y = SkScalarToDouble(p2.fY);
2412
2413 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2414 (p1y - p0y) * (p2x - p0x));
2415
2416 }
2417 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002418}
2419
reed@google.comc1ea60a2012-01-31 15:15:36 +00002420// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002421static int find_max_y(const SkPoint pts[], int count) {
2422 SkASSERT(count > 0);
2423 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002424 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002425 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002426 SkScalar y = pts[i].fY;
2427 if (y > max) {
2428 max = y;
2429 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002430 }
2431 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002432 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002433}
2434
reed@google.comcabaf1d2012-01-11 21:03:05 +00002435static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2436 int i = index;
2437 for (;;) {
2438 i = (i + inc) % n;
2439 if (i == index) { // we wrapped around, so abort
2440 break;
2441 }
2442 if (pts[index] != pts[i]) { // found a different point, success!
2443 break;
2444 }
2445 }
2446 return i;
2447}
2448
reed@google.comc1ea60a2012-01-31 15:15:36 +00002449/**
2450 * Starting at index, and moving forward (incrementing), find the xmin and
2451 * xmax of the contiguous points that have the same Y.
2452 */
2453static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2454 int* maxIndexPtr) {
2455 const SkScalar y = pts[index].fY;
2456 SkScalar min = pts[index].fX;
2457 SkScalar max = min;
2458 int minIndex = index;
2459 int maxIndex = index;
2460 for (int i = index + 1; i < n; ++i) {
2461 if (pts[i].fY != y) {
2462 break;
2463 }
2464 SkScalar x = pts[i].fX;
2465 if (x < min) {
2466 min = x;
2467 minIndex = i;
2468 } else if (x > max) {
2469 max = x;
2470 maxIndex = i;
2471 }
2472 }
2473 *maxIndexPtr = maxIndex;
2474 return minIndex;
2475}
2476
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002477static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002478 if (dir) {
2479 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2480 }
reed@google.comac8543f2012-01-30 20:51:25 +00002481}
2482
reed@google.comc1ea60a2012-01-31 15:15:36 +00002483#if 0
2484#include "SkString.h"
2485#include "../utils/SkParsePath.h"
2486static void dumpPath(const SkPath& path) {
2487 SkString str;
2488 SkParsePath::ToSVGString(path, &str);
2489 SkDebugf("%s\n", str.c_str());
2490}
2491#endif
2492
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002493namespace {
2494// for use with convex_dir_test
2495double mul(double a, double b) { return a * b; }
2496SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2497double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2498SkScalar toScalar(SkScalar a) { return a; }
2499
2500// determines the winding direction of a convex polygon with the precision
2501// of T. CAST_SCALAR casts an SkScalar to T.
2502template <typename T, T (CAST_SCALAR)(SkScalar)>
2503bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2504 // we find the first three points that form a non-degenerate
2505 // triangle. If there are no such points then the path is
2506 // degenerate. The first is always point 0. Now we find the second
2507 // point.
2508 int i = 0;
2509 enum { kX = 0, kY = 1 };
2510 T v0[2];
2511 while (1) {
2512 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2513 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2514 if (v0[kX] || v0[kY]) {
2515 break;
2516 }
2517 if (++i == n - 1) {
2518 return false;
2519 }
2520 }
2521 // now find a third point that is not colinear with the first two
2522 // points and check the orientation of the triangle (which will be
2523 // the same as the orientation of the path).
2524 for (++i; i < n; ++i) {
2525 T v1[2];
2526 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2527 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2528 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2529 if (0 != cross) {
2530 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2531 return true;
2532 }
2533 }
2534 return false;
2535}
2536}
2537
reed@google.comac8543f2012-01-30 20:51:25 +00002538/*
2539 * We loop through all contours, and keep the computed cross-product of the
2540 * contour that contained the global y-max. If we just look at the first
2541 * contour, we may find one that is wound the opposite way (correctly) since
2542 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2543 * that is outer most (or at least has the global y-max) before we can consider
2544 * its cross product.
2545 */
reed@google.com69a99432012-01-10 18:00:10 +00002546bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002547// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002548 // don't want to pay the cost for computing this if it
2549 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002550
2551 if (kUnknown_Direction != fDirection) {
2552 *dir = static_cast<Direction>(fDirection);
2553 return true;
2554 }
reed@google.com69a99432012-01-10 18:00:10 +00002555 const Convexity conv = this->getConvexityOrUnknown();
2556
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002557 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002558
reed@google.comac8543f2012-01-30 20:51:25 +00002559 // initialize with our logical y-min
2560 SkScalar ymax = this->getBounds().fTop;
2561 SkScalar ymaxCross = 0;
2562
reed@google.com69a99432012-01-10 18:00:10 +00002563 for (; !iter.done(); iter.next()) {
2564 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002565 if (n < 3) {
2566 continue;
2567 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002568
reed@google.comcabaf1d2012-01-11 21:03:05 +00002569 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002570 SkScalar cross = 0;
2571 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002572 // We try first at scalar precision, and then again at double
2573 // precision. This is because the vectors computed between distant
2574 // points may lose too much precision.
2575 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002576 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002577 return true;
2578 }
2579 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002580 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002581 return true;
2582 } else {
2583 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002584 }
2585 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002586 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002587 if (pts[index].fY < ymax) {
2588 continue;
2589 }
2590
reed@google.comc1ea60a2012-01-31 15:15:36 +00002591 // If there is more than 1 distinct point at the y-max, we take the
2592 // x-min and x-max of them and just subtract to compute the dir.
2593 if (pts[(index + 1) % n].fY == pts[index].fY) {
2594 int maxIndex;
2595 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002596 if (minIndex == maxIndex) {
2597 goto TRY_CROSSPROD;
2598 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002599 SkASSERT(pts[minIndex].fY == pts[index].fY);
2600 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2601 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2602 // we just subtract the indices, and let that auto-convert to
2603 // SkScalar, since we just want - or + to signal the direction.
2604 cross = minIndex - maxIndex;
2605 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002606 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002607 // Find a next and prev index to use for the cross-product test,
2608 // but we try to find pts that form non-zero vectors from pts[index]
2609 //
2610 // Its possible that we can't find two non-degenerate vectors, so
2611 // we have to guard our search (e.g. all the pts could be in the
2612 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002613
reed@google.comc1ea60a2012-01-31 15:15:36 +00002614 // we pass n - 1 instead of -1 so we don't foul up % operator by
2615 // passing it a negative LH argument.
2616 int prev = find_diff_pt(pts, index, n, n - 1);
2617 if (prev == index) {
2618 // completely degenerate, skip to next contour
2619 continue;
2620 }
2621 int next = find_diff_pt(pts, index, n, 1);
2622 SkASSERT(next != index);
2623 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002624 // 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 +00002625 // x-direction. We really should continue to walk away from the degeneracy until
2626 // there is a divergence.
2627 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002628 // construct the subtract so we get the correct Direction below
2629 cross = pts[index].fX - pts[next].fX;
2630 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002631 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002632
reed@google.comac8543f2012-01-30 20:51:25 +00002633 if (cross) {
2634 // record our best guess so far
2635 ymax = pts[index].fY;
2636 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002637 }
reed@google.com69a99432012-01-10 18:00:10 +00002638 }
2639 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002640 if (ymaxCross) {
2641 crossToDir(ymaxCross, dir);
2642 fDirection = *dir;
2643 return true;
2644 } else {
2645 return false;
2646 }
reed@google.comac8543f2012-01-30 20:51:25 +00002647}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002648
2649///////////////////////////////////////////////////////////////////////////////
2650
2651static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2652 SkScalar D, SkScalar t) {
2653 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2654}
2655
2656static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2657 SkScalar t) {
2658 SkScalar A = c3 + 3*(c1 - c2) - c0;
2659 SkScalar B = 3*(c2 - c1 - c1 + c0);
2660 SkScalar C = 3*(c1 - c0);
2661 SkScalar D = c0;
2662 return eval_cubic_coeff(A, B, C, D, t);
2663}
2664
2665/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2666 t value such that cubic(t) = target
2667 */
2668static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2669 SkScalar target, SkScalar* t) {
2670 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2671 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002672
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002673 SkScalar D = c0 - target;
2674 SkScalar A = c3 + 3*(c1 - c2) - c0;
2675 SkScalar B = 3*(c2 - c1 - c1 + c0);
2676 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002677
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002678 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2679 SkScalar minT = 0;
2680 SkScalar maxT = SK_Scalar1;
2681 SkScalar mid;
2682 int i;
2683 for (i = 0; i < 16; i++) {
2684 mid = SkScalarAve(minT, maxT);
2685 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2686 if (delta < 0) {
2687 minT = mid;
2688 delta = -delta;
2689 } else {
2690 maxT = mid;
2691 }
2692 if (delta < TOLERANCE) {
2693 break;
2694 }
2695 }
2696 *t = mid;
2697 return true;
2698}
2699
2700template <size_t N> static void find_minmax(const SkPoint pts[],
2701 SkScalar* minPtr, SkScalar* maxPtr) {
2702 SkScalar min, max;
2703 min = max = pts[0].fX;
2704 for (size_t i = 1; i < N; ++i) {
2705 min = SkMinScalar(min, pts[i].fX);
2706 max = SkMaxScalar(max, pts[i].fX);
2707 }
2708 *minPtr = min;
2709 *maxPtr = max;
2710}
2711
2712static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2713 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002714
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002715 int dir = 1;
2716 if (pts[0].fY > pts[3].fY) {
2717 storage[0] = pts[3];
2718 storage[1] = pts[2];
2719 storage[2] = pts[1];
2720 storage[3] = pts[0];
2721 pts = storage;
2722 dir = -1;
2723 }
2724 if (y < pts[0].fY || y >= pts[3].fY) {
2725 return 0;
2726 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 // quickreject or quickaccept
2729 SkScalar min, max;
2730 find_minmax<4>(pts, &min, &max);
2731 if (x < min) {
2732 return 0;
2733 }
2734 if (x > max) {
2735 return dir;
2736 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002737
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002738 // compute the actual x(t) value
2739 SkScalar t, xt;
2740 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2741 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2742 } else {
2743 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2744 xt = y < mid ? pts[0].fX : pts[3].fX;
2745 }
2746 return xt < x ? dir : 0;
2747}
2748
2749static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2750 SkPoint dst[10];
2751 int n = SkChopCubicAtYExtrema(pts, dst);
2752 int w = 0;
2753 for (int i = 0; i <= n; ++i) {
2754 w += winding_mono_cubic(&dst[i * 3], x, y);
2755 }
2756 return w;
2757}
2758
2759static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2760 SkScalar y0 = pts[0].fY;
2761 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 int dir = 1;
2764 if (y0 > y2) {
2765 SkTSwap(y0, y2);
2766 dir = -1;
2767 }
2768 if (y < y0 || y >= y2) {
2769 return 0;
2770 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002771
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772 // bounds check on X (not required. is it faster?)
2773#if 0
2774 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2775 return 0;
2776 }
2777#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002778
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002779 SkScalar roots[2];
2780 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2781 2 * (pts[1].fY - pts[0].fY),
2782 pts[0].fY - y,
2783 roots);
2784 SkASSERT(n <= 1);
2785 SkScalar xt;
2786 if (0 == n) {
2787 SkScalar mid = SkScalarAve(y0, y2);
2788 // Need [0] and [2] if dir == 1
2789 // and [2] and [0] if dir == -1
2790 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2791 } else {
2792 SkScalar t = roots[0];
2793 SkScalar C = pts[0].fX;
2794 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2795 SkScalar B = 2 * (pts[1].fX - C);
2796 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2797 }
2798 return xt < x ? dir : 0;
2799}
2800
2801static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2802 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2803 if (y0 == y1) {
2804 return true;
2805 }
2806 if (y0 < y1) {
2807 return y1 <= y2;
2808 } else {
2809 return y1 >= y2;
2810 }
2811}
2812
2813static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2814 SkPoint dst[5];
2815 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2818 n = SkChopQuadAtYExtrema(pts, dst);
2819 pts = dst;
2820 }
2821 int w = winding_mono_quad(pts, x, y);
2822 if (n > 0) {
2823 w += winding_mono_quad(&pts[2], x, y);
2824 }
2825 return w;
2826}
2827
2828static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2829 SkScalar x0 = pts[0].fX;
2830 SkScalar y0 = pts[0].fY;
2831 SkScalar x1 = pts[1].fX;
2832 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002833
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 int dir = 1;
2837 if (y0 > y1) {
2838 SkTSwap(y0, y1);
2839 dir = -1;
2840 }
2841 if (y < y0 || y >= y1) {
2842 return 0;
2843 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002844
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2846 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002847
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 if (SkScalarSignAsInt(cross) == dir) {
2849 dir = 0;
2850 }
2851 return dir;
2852}
2853
2854bool SkPath::contains(SkScalar x, SkScalar y) const {
2855 bool isInverse = this->isInverseFillType();
2856 if (this->isEmpty()) {
2857 return isInverse;
2858 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 const SkRect& bounds = this->getBounds();
2861 if (!bounds.contains(x, y)) {
2862 return isInverse;
2863 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002864
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865 SkPath::Iter iter(*this, true);
2866 bool done = false;
2867 int w = 0;
2868 do {
2869 SkPoint pts[4];
2870 switch (iter.next(pts, false)) {
2871 case SkPath::kMove_Verb:
2872 case SkPath::kClose_Verb:
2873 break;
2874 case SkPath::kLine_Verb:
2875 w += winding_line(pts, x, y);
2876 break;
2877 case SkPath::kQuad_Verb:
2878 w += winding_quad(pts, x, y);
2879 break;
2880 case SkPath::kCubic_Verb:
2881 w += winding_cubic(pts, x, y);
2882 break;
2883 case SkPath::kDone_Verb:
2884 done = true;
2885 break;
2886 }
2887 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002888
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 switch (this->getFillType()) {
2890 case SkPath::kEvenOdd_FillType:
2891 case SkPath::kInverseEvenOdd_FillType:
2892 w &= 1;
2893 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002894 default:
2895 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002896 }
2897 return SkToBool(w);
2898}
2899