blob: d4ef1a6044d5490125e7f17f851687324aa6977f [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
djsollen@google.com56c69772011-11-08 19:00:26 +0000314#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000315uint32_t SkPath::getGenerationID() const {
316 return fGenerationID;
317}
djsollen@google.come63793a2012-03-21 15:39:03 +0000318
319const SkPath* SkPath::getSourcePath() const {
320 return fSourcePath;
321}
322
323void SkPath::setSourcePath(const SkPath* path) {
324 fSourcePath = path;
325}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000326#endif
327
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328void SkPath::reset() {
329 SkDEBUGCODE(this->validate();)
330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000331 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000332 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000333 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000334 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000335 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000336 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000337 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000338 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339}
340
341void SkPath::rewind() {
342 SkDEBUGCODE(this->validate();)
343
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000344 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000345 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000346 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000347 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000348 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000349 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000350 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
353bool SkPath::isEmpty() const {
354 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000355 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356}
357
reed@google.com7e6c4d12012-05-10 14:05:43 +0000358bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000359 int verbCount = fPathRef->countVerbs();
360 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000361
reed@google.com7e6c4d12012-05-10 14:05:43 +0000362 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 if (kMove_Verb == fPathRef->atVerb(0) &&
364 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000365 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000366 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000367 line[0] = pts[0];
368 line[1] = pts[1];
369 }
370 return true;
371 }
372 }
373 return false;
374}
375
caryclark@google.comf1316942011-07-26 19:54:45 +0000376/*
377 Determines if path is a rect by keeping track of changes in direction
378 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000379
caryclark@google.comf1316942011-07-26 19:54:45 +0000380 The direction is computed such that:
381 0: vertical up
382 1: horizontal right
383 2: vertical down
384 3: horizontal left
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000385
caryclark@google.comf1316942011-07-26 19:54:45 +0000386A rectangle cycles up/right/down/left or up/left/down/right.
387
388The test fails if:
389 The path is closed, and followed by a line.
390 A second move creates a new endpoint.
391 A diagonal line is parsed.
392 There's more than four changes of direction.
393 There's a discontinuity on the line (e.g., a move in the middle)
394 The line reverses direction.
395 The rectangle doesn't complete a cycle.
396 The path contains a quadratic or cubic.
397 The path contains fewer than four points.
398 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400It's OK if the path has:
401 Several colinear line segments composing a rectangle side.
402 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404The direction takes advantage of the corners found since opposite sides
405must travel in opposite directions.
406
407FIXME: Allow colinear quads and cubics to be treated like lines.
408FIXME: If the API passes fill-only, return true if the filled stroke
409 is a rectangle, though the caller failed to close the path.
410 */
411bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000413
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 int corners = 0;
415 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000416 first.set(0, 0);
417 last.set(0, 0);
418 int firstDirection = 0;
419 int lastDirection = 0;
420 int nextDirection = 0;
421 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000423 const SkPoint* pts = fPathRef->points();
424 int verbCnt = fPathRef->countVerbs();
425 int currVerb = 0;
426 while (currVerb < verbCnt) {
427 switch (fPathRef->atVerb(currVerb++)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 case kClose_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000429 pts = fPathRef->points();
caryclark@google.comf1316942011-07-26 19:54:45 +0000430 autoClose = true;
431 case kLine_Verb: {
432 SkScalar left = last.fX;
433 SkScalar top = last.fY;
434 SkScalar right = pts->fX;
435 SkScalar bottom = pts->fY;
436 ++pts;
437 if (left != right && top != bottom) {
438 return false; // diagonal
439 }
440 if (left == right && top == bottom) {
441 break; // single point on side OK
442 }
443 nextDirection = (left != right) << 0 |
444 (left < right || top < bottom) << 1;
445 if (0 == corners) {
446 firstDirection = nextDirection;
447 first = last;
448 last = pts[-1];
449 corners = 1;
450 closedOrMoved = false;
451 break;
452 }
453 if (closedOrMoved) {
454 return false; // closed followed by a line
455 }
456 closedOrMoved = autoClose;
457 if (lastDirection != nextDirection) {
458 if (++corners > 4) {
459 return false; // too many direction changes
460 }
461 }
462 last = pts[-1];
463 if (lastDirection == nextDirection) {
464 break; // colinear segment
465 }
466 // Possible values for corners are 2, 3, and 4.
467 // When corners == 3, nextDirection opposes firstDirection.
468 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000469 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
471 if ((directionCycle ^ turn) != nextDirection) {
472 return false; // direction didn't follow cycle
473 }
474 break;
475 }
476 case kQuad_Verb:
477 case kCubic_Verb:
478 return false; // quadratic, cubic not allowed
479 case kMove_Verb:
480 last = *pts++;
481 closedOrMoved = true;
482 break;
483 }
484 lastDirection = nextDirection;
485 }
486 // Success if 4 corners and first point equals last
487 bool result = 4 == corners && first == last;
488 if (result && rect) {
489 *rect = getBounds();
490 }
491 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000492}
493
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000494int SkPath::countPoints() const {
495 return fPathRef->countPoints();
496}
497
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000498int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000499 SkDEBUGCODE(this->validate();)
500
501 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000502 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000503 int count = SkMin32(max, fPathRef->countPoints());
504 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
505 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000506}
507
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000508SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000509 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
510 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000511 }
512 return SkPoint::Make(0, 0);
513}
514
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000515int SkPath::countVerbs() const {
516 return fPathRef->countVerbs();
517}
518
519static inline void copy_verbs_reverse(uint8_t* inorderDst,
520 const uint8_t* reversedSrc,
521 int count) {
522 for (int i = 0; i < count; ++i) {
523 inorderDst[i] = reversedSrc[~i];
524 }
525}
526
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000527int SkPath::getVerbs(uint8_t dst[], int max) const {
528 SkDEBUGCODE(this->validate();)
529
530 SkASSERT(max >= 0);
531 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000532 int count = SkMin32(max, fPathRef->countVerbs());
533 copy_verbs_reverse(dst, fPathRef->verbs(), count);
534 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000535}
536
reed@google.com294dd7b2011-10-11 11:58:32 +0000537bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538 SkDEBUGCODE(this->validate();)
539
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000540 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000541 if (count > 0) {
542 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000543 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000545 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000547 if (lastPt) {
548 lastPt->set(0, 0);
549 }
550 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551}
552
553void SkPath::setLastPt(SkScalar x, SkScalar y) {
554 SkDEBUGCODE(this->validate();)
555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000556 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557 if (count == 0) {
558 this->moveTo(x, y);
559 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000560 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000561 SkPathRef::Editor ed(&fPathRef);
562 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000563 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 }
565}
566
reed@android.comd252db02009-04-01 18:31:44 +0000567void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000568 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000569 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000571 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000572 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573}
574
reed@google.com04863fa2011-05-15 04:08:24 +0000575void SkPath::setConvexity(Convexity c) {
576 if (fConvexity != c) {
577 fConvexity = c;
578 GEN_ID_INC;
579 }
580}
581
reed@android.com8a1c16f2008-12-17 15:59:43 +0000582//////////////////////////////////////////////////////////////////////////////
583// Construction methods
584
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000585#define DIRTY_AFTER_EDIT \
586 do { \
587 fBoundsIsDirty = true; \
588 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000589 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000590 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000591 } while (0)
592
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000593#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
594 do { \
595 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000596 } while (0)
597
reed@android.com8a1c16f2008-12-17 15:59:43 +0000598void SkPath::incReserve(U16CPU inc) {
599 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000600 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 SkDEBUGCODE(this->validate();)
602}
603
604void SkPath::moveTo(SkScalar x, SkScalar y) {
605 SkDEBUGCODE(this->validate();)
606
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000607 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608
reed@google.comd335d1d2012-01-12 18:17:11 +0000609 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000611
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000612 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000613
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000614 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000615 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616}
617
618void SkPath::rMoveTo(SkScalar x, SkScalar y) {
619 SkPoint pt;
620 this->getLastPt(&pt);
621 this->moveTo(pt.fX + x, pt.fY + y);
622}
623
reed@google.comd335d1d2012-01-12 18:17:11 +0000624void SkPath::injectMoveToIfNeeded() {
625 if (fLastMoveToIndex < 0) {
626 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000628 x = y = 0;
629 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000631 x = pt.fX;
632 y = pt.fY;
633 }
634 this->moveTo(x, y);
635 }
636}
637
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638void SkPath::lineTo(SkScalar x, SkScalar y) {
639 SkDEBUGCODE(this->validate();)
640
reed@google.comd335d1d2012-01-12 18:17:11 +0000641 this->injectMoveToIfNeeded();
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 SkPathRef::Editor ed(&fPathRef);
644 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000645 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000647 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000648 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649}
650
651void SkPath::rLineTo(SkScalar x, SkScalar y) {
652 SkPoint pt;
653 this->getLastPt(&pt);
654 this->lineTo(pt.fX + x, pt.fY + y);
655}
656
657void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
658 SkDEBUGCODE(this->validate();)
659
reed@google.comd335d1d2012-01-12 18:17:11 +0000660 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
663 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 pts[0].set(x1, y1);
665 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000666 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000668 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000669 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670}
671
672void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
673 SkPoint pt;
674 this->getLastPt(&pt);
675 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
676}
677
678void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
679 SkScalar x3, SkScalar y3) {
680 SkDEBUGCODE(this->validate();)
681
reed@google.comd335d1d2012-01-12 18:17:11 +0000682 this->injectMoveToIfNeeded();
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 SkPathRef::Editor ed(&fPathRef);
685 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 pts[0].set(x1, y1);
687 pts[1].set(x2, y2);
688 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000689 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000691 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000692 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693}
694
695void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
696 SkScalar x3, SkScalar y3) {
697 SkPoint pt;
698 this->getLastPt(&pt);
699 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
700 pt.fX + x3, pt.fY + y3);
701}
702
703void SkPath::close() {
704 SkDEBUGCODE(this->validate();)
705
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000706 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000708 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 case kLine_Verb:
710 case kQuad_Verb:
711 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 case kMove_Verb: {
713 SkPathRef::Editor ed(&fPathRef);
714 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000715 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000719 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 break;
721 }
722 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000723
724 // signal that we need a moveTo to follow us (unless we're done)
725#if 0
726 if (fLastMoveToIndex >= 0) {
727 fLastMoveToIndex = ~fLastMoveToIndex;
728 }
729#else
730 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
731#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732}
733
734///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736void SkPath::addRect(const SkRect& rect, Direction dir) {
737 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
738}
739
740void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
741 SkScalar bottom, Direction dir) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000742 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
743 SkAutoDisableDirectionCheck addc(this);
744
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
746
747 this->incReserve(5);
748
749 this->moveTo(left, top);
750 if (dir == kCCW_Direction) {
751 this->lineTo(left, bottom);
752 this->lineTo(right, bottom);
753 this->lineTo(right, top);
754 } else {
755 this->lineTo(right, top);
756 this->lineTo(right, bottom);
757 this->lineTo(left, bottom);
758 }
759 this->close();
760}
761
reed@google.com744faba2012-05-29 19:54:52 +0000762void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
763 SkDEBUGCODE(this->validate();)
764 if (count <= 0) {
765 return;
766 }
767
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000768 SkPathRef::Editor ed(&fPathRef);
769 fLastMoveToIndex = ed.pathRef()->countPoints();
770 uint8_t* vb;
771 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000772 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000773 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000775 memcpy(p, pts, count * sizeof(SkPoint));
776 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000777 if (count > 1) {
778 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
779 // be 0, the compiler will remove the test/branch entirely.
780 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000781 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000782 } else {
783 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000784 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000785 }
786 }
787 fSegmentMask |= kLine_SegmentMask;
788 }
789 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000790 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000791 }
792
793 GEN_ID_INC;
794 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000796}
797
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
799
800void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
801 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 SkScalar w = rect.width();
803 SkScalar halfW = SkScalarHalf(w);
804 SkScalar h = rect.height();
805 SkScalar halfH = SkScalarHalf(h);
806
807 if (halfW <= 0 || halfH <= 0) {
808 return;
809 }
810
reed@google.comabf15c12011-01-18 20:35:51 +0000811 bool skip_hori = rx >= halfW;
812 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813
814 if (skip_hori && skip_vert) {
815 this->addOval(rect, dir);
816 return;
817 }
reed@google.comabf15c12011-01-18 20:35:51 +0000818
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000819 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
820
reed@google.comabf15c12011-01-18 20:35:51 +0000821 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000822 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 if (skip_hori) {
825 rx = halfW;
826 } else if (skip_vert) {
827 ry = halfH;
828 }
829
830 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
831 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
832
833 this->incReserve(17);
834 this->moveTo(rect.fRight - rx, rect.fTop);
835 if (dir == kCCW_Direction) {
836 if (!skip_hori) {
837 this->lineTo(rect.fLeft + rx, rect.fTop); // top
838 }
839 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
840 rect.fLeft, rect.fTop + ry - sy,
841 rect.fLeft, rect.fTop + ry); // top-left
842 if (!skip_vert) {
843 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
844 }
845 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
846 rect.fLeft + rx - sx, rect.fBottom,
847 rect.fLeft + rx, rect.fBottom); // bot-left
848 if (!skip_hori) {
849 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
850 }
851 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
852 rect.fRight, rect.fBottom - ry + sy,
853 rect.fRight, rect.fBottom - ry); // bot-right
854 if (!skip_vert) {
855 this->lineTo(rect.fRight, rect.fTop + ry);
856 }
857 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
858 rect.fRight - rx + sx, rect.fTop,
859 rect.fRight - rx, rect.fTop); // top-right
860 } else {
861 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
862 rect.fRight, rect.fTop + ry - sy,
863 rect.fRight, rect.fTop + ry); // top-right
864 if (!skip_vert) {
865 this->lineTo(rect.fRight, rect.fBottom - ry);
866 }
867 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
868 rect.fRight - rx + sx, rect.fBottom,
869 rect.fRight - rx, rect.fBottom); // bot-right
870 if (!skip_hori) {
871 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
872 }
873 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
874 rect.fLeft, rect.fBottom - ry + sy,
875 rect.fLeft, rect.fBottom - ry); // bot-left
876 if (!skip_vert) {
877 this->lineTo(rect.fLeft, rect.fTop + ry); // left
878 }
879 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
880 rect.fLeft + rx - sx, rect.fTop,
881 rect.fLeft + rx, rect.fTop); // top-left
882 if (!skip_hori) {
883 this->lineTo(rect.fRight - rx, rect.fTop); // top
884 }
885 }
886 this->close();
887}
888
889static void add_corner_arc(SkPath* path, const SkRect& rect,
890 SkScalar rx, SkScalar ry, int startAngle,
891 SkPath::Direction dir, bool forceMoveTo) {
892 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
893 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000894
reed@android.com8a1c16f2008-12-17 15:59:43 +0000895 SkRect r;
896 r.set(-rx, -ry, rx, ry);
897
898 switch (startAngle) {
899 case 0:
900 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
901 break;
902 case 90:
903 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
904 break;
905 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
906 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000907 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 }
reed@google.comabf15c12011-01-18 20:35:51 +0000909
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910 SkScalar start = SkIntToScalar(startAngle);
911 SkScalar sweep = SkIntToScalar(90);
912 if (SkPath::kCCW_Direction == dir) {
913 start += sweep;
914 sweep = -sweep;
915 }
reed@google.comabf15c12011-01-18 20:35:51 +0000916
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917 path->arcTo(r, start, sweep, forceMoveTo);
918}
919
920void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
921 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000922 // abort before we invoke SkAutoPathBoundsUpdate()
923 if (rect.isEmpty()) {
924 return;
925 }
926
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927 SkAutoPathBoundsUpdate apbu(this, rect);
928
929 if (kCW_Direction == dir) {
930 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
931 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
932 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
933 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
934 } else {
935 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
936 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
937 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
938 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
939 }
940 this->close();
941}
942
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000943bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000944 int count = fPathRef->countVerbs();
945 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
946 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000947 if (*verbs == kLine_Verb ||
948 *verbs == kQuad_Verb ||
949 *verbs == kCubic_Verb) {
950 return false;
951 }
952 ++verbs;
953 }
954 return true;
955}
956
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000958 /* If addOval() is called after previous moveTo(),
959 this path is still marked as an oval. This is used to
960 fit into WebKit's calling sequences.
961 We can't simply check isEmpty() in this case, as additional
962 moveTo() would mark the path non empty.
963 */
964 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000965 if (fIsOval) {
966 fDirection = dir;
967 } else {
968 fDirection = kUnknown_Direction;
969 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000970
971 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000972 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000973
reed@android.com8a1c16f2008-12-17 15:59:43 +0000974 SkAutoPathBoundsUpdate apbu(this, oval);
975
976 SkScalar cx = oval.centerX();
977 SkScalar cy = oval.centerY();
978 SkScalar rx = SkScalarHalf(oval.width());
979 SkScalar ry = SkScalarHalf(oval.height());
980#if 0 // these seem faster than using quads (1/2 the number of edges)
981 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
982 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
983
984 this->incReserve(13);
985 this->moveTo(cx + rx, cy);
986 if (dir == kCCW_Direction) {
987 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
988 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
989 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
990 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
991 } else {
992 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
993 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
994 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
995 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
996 }
997#else
998 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
999 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1000 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1001 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1002
1003 /*
1004 To handle imprecision in computing the center and radii, we revert to
1005 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1006 to ensure that we don't exceed the oval's bounds *ever*, since we want
1007 to use oval for our fast-bounds, rather than have to recompute it.
1008 */
1009 const SkScalar L = oval.fLeft; // cx - rx
1010 const SkScalar T = oval.fTop; // cy - ry
1011 const SkScalar R = oval.fRight; // cx + rx
1012 const SkScalar B = oval.fBottom; // cy + ry
1013
1014 this->incReserve(17); // 8 quads + close
1015 this->moveTo(R, cy);
1016 if (dir == kCCW_Direction) {
1017 this->quadTo( R, cy - sy, cx + mx, cy - my);
1018 this->quadTo(cx + sx, T, cx , T);
1019 this->quadTo(cx - sx, T, cx - mx, cy - my);
1020 this->quadTo( L, cy - sy, L, cy );
1021 this->quadTo( L, cy + sy, cx - mx, cy + my);
1022 this->quadTo(cx - sx, B, cx , B);
1023 this->quadTo(cx + sx, B, cx + mx, cy + my);
1024 this->quadTo( R, cy + sy, R, cy );
1025 } else {
1026 this->quadTo( R, cy + sy, cx + mx, cy + my);
1027 this->quadTo(cx + sx, B, cx , B);
1028 this->quadTo(cx - sx, B, cx - mx, cy + my);
1029 this->quadTo( L, cy + sy, L, cy );
1030 this->quadTo( L, cy - sy, cx - mx, cy - my);
1031 this->quadTo(cx - sx, T, cx , T);
1032 this->quadTo(cx + sx, T, cx + mx, cy - my);
1033 this->quadTo( R, cy - sy, R, cy );
1034 }
1035#endif
1036 this->close();
1037}
1038
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001039bool SkPath::isOval(SkRect* rect) const {
1040 if (fIsOval && rect) {
1041 *rect = getBounds();
1042 }
1043
1044 return fIsOval;
1045}
1046
reed@android.com8a1c16f2008-12-17 15:59:43 +00001047void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1048 if (r > 0) {
1049 SkRect rect;
1050 rect.set(x - r, y - r, x + r, y + r);
1051 this->addOval(rect, dir);
1052 }
1053}
1054
1055#include "SkGeometry.h"
1056
1057static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1058 SkScalar sweepAngle,
1059 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001060
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001061 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001062 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1063 // Chrome uses this path to move into and out of ovals. If not
1064 // treated as a special case the moves can distort the oval's
1065 // bounding box (and break the circle special case).
1066 pts[0].set(oval.fRight, oval.centerY());
1067 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001068 } else if (0 == oval.width() && 0 == oval.height()) {
1069 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001070 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001071 // a rect.
1072 // TODO: optimizing the case where only one of width or height is zero
1073 // should also be considered. This case, however, doesn't seem to be
1074 // as common as the single point case.
1075 pts[0].set(oval.fRight, oval.fTop);
1076 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001077 }
1078
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 SkVector start, stop;
1080
1081 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1082 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1083 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001084
1085 /* If the sweep angle is nearly (but less than) 360, then due to precision
1086 loss in radians-conversion and/or sin/cos, we may end up with coincident
1087 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1088 of drawing a nearly complete circle (good).
1089 e.g. canvas.drawArc(0, 359.99, ...)
1090 -vs- canvas.drawArc(0, 359.9, ...)
1091 We try to detect this edge case, and tweak the stop vector
1092 */
1093 if (start == stop) {
1094 SkScalar sw = SkScalarAbs(sweepAngle);
1095 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1096 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1097 // make a guess at a tiny angle (in radians) to tweak by
1098 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1099 // not sure how much will be enough, so we use a loop
1100 do {
1101 stopRad -= deltaRad;
1102 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1103 } while (start == stop);
1104 }
1105 }
1106
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001108
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1110 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001111
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 return SkBuildQuadArc(start, stop,
1113 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1114 &matrix, pts);
1115}
1116
1117void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1118 bool forceMoveTo) {
1119 if (oval.width() < 0 || oval.height() < 0) {
1120 return;
1121 }
1122
1123 SkPoint pts[kSkBuildQuadArcStorage];
1124 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1125 SkASSERT((count & 1) == 1);
1126
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001127 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 forceMoveTo = true;
1129 }
1130 this->incReserve(count);
1131 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1132 for (int i = 1; i < count; i += 2) {
1133 this->quadTo(pts[i], pts[i+1]);
1134 }
1135}
1136
1137void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1138 SkScalar sweepAngle) {
1139 if (oval.isEmpty() || 0 == sweepAngle) {
1140 return;
1141 }
1142
1143 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1144
1145 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1146 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1147 return;
1148 }
1149
1150 SkPoint pts[kSkBuildQuadArcStorage];
1151 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1152
1153 this->incReserve(count);
1154 this->moveTo(pts[0]);
1155 for (int i = 1; i < count; i += 2) {
1156 this->quadTo(pts[i], pts[i+1]);
1157 }
1158}
1159
1160/*
1161 Need to handle the case when the angle is sharp, and our computed end-points
1162 for the arc go behind pt1 and/or p2...
1163*/
1164void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1165 SkScalar radius) {
1166 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001167
reed@android.com8a1c16f2008-12-17 15:59:43 +00001168 // need to know our prev pt so we can construct tangent vectors
1169 {
1170 SkPoint start;
1171 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001172 // Handle degenerate cases by adding a line to the first point and
1173 // bailing out.
1174 if ((x1 == start.fX && y1 == start.fY) ||
1175 (x1 == x2 && y1 == y2) ||
1176 radius == 0) {
1177 this->lineTo(x1, y1);
1178 return;
1179 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 before.setNormalize(x1 - start.fX, y1 - start.fY);
1181 after.setNormalize(x2 - x1, y2 - y1);
1182 }
reed@google.comabf15c12011-01-18 20:35:51 +00001183
reed@android.com8a1c16f2008-12-17 15:59:43 +00001184 SkScalar cosh = SkPoint::DotProduct(before, after);
1185 SkScalar sinh = SkPoint::CrossProduct(before, after);
1186
1187 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001188 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 return;
1190 }
reed@google.comabf15c12011-01-18 20:35:51 +00001191
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1193 if (dist < 0) {
1194 dist = -dist;
1195 }
1196
1197 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1198 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1199 SkRotationDirection arcDir;
1200
1201 // now turn before/after into normals
1202 if (sinh > 0) {
1203 before.rotateCCW();
1204 after.rotateCCW();
1205 arcDir = kCW_SkRotationDirection;
1206 } else {
1207 before.rotateCW();
1208 after.rotateCW();
1209 arcDir = kCCW_SkRotationDirection;
1210 }
1211
1212 SkMatrix matrix;
1213 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001214
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 matrix.setScale(radius, radius);
1216 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1217 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001218
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001220
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 this->incReserve(count);
1222 // [xx,yy] == pts[0]
1223 this->lineTo(xx, yy);
1224 for (int i = 1; i < count; i += 2) {
1225 this->quadTo(pts[i], pts[i+1]);
1226 }
1227}
1228
1229///////////////////////////////////////////////////////////////////////////////
1230
1231void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1232 SkMatrix matrix;
1233
1234 matrix.setTranslate(dx, dy);
1235 this->addPath(path, matrix);
1236}
1237
1238void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001239 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001241 fIsOval = false;
1242
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001243 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkPoint pts[4];
1245 Verb verb;
1246
1247 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1248
1249 while ((verb = iter.next(pts)) != kDone_Verb) {
1250 switch (verb) {
1251 case kMove_Verb:
1252 proc(matrix, &pts[0], &pts[0], 1);
1253 this->moveTo(pts[0]);
1254 break;
1255 case kLine_Verb:
1256 proc(matrix, &pts[1], &pts[1], 1);
1257 this->lineTo(pts[1]);
1258 break;
1259 case kQuad_Verb:
1260 proc(matrix, &pts[1], &pts[1], 2);
1261 this->quadTo(pts[1], pts[2]);
1262 break;
1263 case kCubic_Verb:
1264 proc(matrix, &pts[1], &pts[1], 3);
1265 this->cubicTo(pts[1], pts[2], pts[3]);
1266 break;
1267 case kClose_Verb:
1268 this->close();
1269 break;
1270 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001271 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 }
1273 }
1274}
1275
1276///////////////////////////////////////////////////////////////////////////////
1277
1278static const uint8_t gPtsInVerb[] = {
1279 1, // kMove
1280 1, // kLine
1281 2, // kQuad
1282 3, // kCubic
1283 0, // kClose
1284 0 // kDone
1285};
1286
1287// ignore the initial moveto, and stop when the 1st contour ends
1288void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001289 int i, vcount = path.fPathRef->countVerbs();
1290 // exit early if the path is empty, or just has a moveTo.
1291 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 return;
1293 }
1294
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001295 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001297 fIsOval = false;
1298
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001299 const uint8_t* verbs = path.fPathRef->verbs();
1300 // skip the initial moveTo
1301 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001303 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001305 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 case kLine_Verb:
1307 this->lineTo(pts[0].fX, pts[0].fY);
1308 break;
1309 case kQuad_Verb:
1310 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1311 break;
1312 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001313 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 +00001314 break;
1315 case kClose_Verb:
1316 return;
1317 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001318 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 }
1320}
1321
1322// ignore the last point of the 1st contour
1323void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001324 int i, vcount = path.fPathRef->countVerbs();
1325 // exit early if the path is empty, or just has a moveTo.
1326 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 return;
1328 }
1329
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001330 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001332 fIsOval = false;
1333
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001334 const uint8_t* verbs = path.fPathRef->verbs();
1335 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001337 SkASSERT(verbs[~0] == kMove_Verb);
1338 for (i = 1; i < vcount; ++i) {
1339 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 if (n == 0) {
1341 break;
1342 }
1343 pts += n;
1344 }
1345
1346 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001347 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 case kLine_Verb:
1349 this->lineTo(pts[-1].fX, pts[-1].fY);
1350 break;
1351 case kQuad_Verb:
1352 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1353 break;
1354 case kCubic_Verb:
1355 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1356 pts[-3].fX, pts[-3].fY);
1357 break;
1358 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001359 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 break;
1361 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001362 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 }
1364}
1365
reed@google.com63d73742012-01-10 15:33:12 +00001366void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001367 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001368
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001369 const SkPoint* pts = src.fPathRef->pointsEnd();
1370 // we will iterator through src's verbs backwards
1371 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1372 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001373
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001374 fIsOval = false;
1375
reed@google.com63d73742012-01-10 15:33:12 +00001376 bool needMove = true;
1377 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001378 while (verbs < verbsEnd) {
1379 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001380 int n = gPtsInVerb[v];
1381
1382 if (needMove) {
1383 --pts;
1384 this->moveTo(pts->fX, pts->fY);
1385 needMove = false;
1386 }
1387 pts -= n;
1388 switch (v) {
1389 case kMove_Verb:
1390 if (needClose) {
1391 this->close();
1392 needClose = false;
1393 }
1394 needMove = true;
1395 pts += 1; // so we see the point in "if (needMove)" above
1396 break;
1397 case kLine_Verb:
1398 this->lineTo(pts[0]);
1399 break;
1400 case kQuad_Verb:
1401 this->quadTo(pts[1], pts[0]);
1402 break;
1403 case kCubic_Verb:
1404 this->cubicTo(pts[2], pts[1], pts[0]);
1405 break;
1406 case kClose_Verb:
1407 needClose = true;
1408 break;
1409 default:
1410 SkASSERT(!"unexpected verb");
1411 }
1412 }
1413}
1414
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415///////////////////////////////////////////////////////////////////////////////
1416
1417void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1418 SkMatrix matrix;
1419
1420 matrix.setTranslate(dx, dy);
1421 this->transform(matrix, dst);
1422}
1423
1424#include "SkGeometry.h"
1425
1426static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1427 int level = 2) {
1428 if (--level >= 0) {
1429 SkPoint tmp[5];
1430
1431 SkChopQuadAtHalf(pts, tmp);
1432 subdivide_quad_to(path, &tmp[0], level);
1433 subdivide_quad_to(path, &tmp[2], level);
1434 } else {
1435 path->quadTo(pts[1], pts[2]);
1436 }
1437}
1438
1439static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1440 int level = 2) {
1441 if (--level >= 0) {
1442 SkPoint tmp[7];
1443
1444 SkChopCubicAtHalf(pts, tmp);
1445 subdivide_cubic_to(path, &tmp[0], level);
1446 subdivide_cubic_to(path, &tmp[3], level);
1447 } else {
1448 path->cubicTo(pts[1], pts[2], pts[3]);
1449 }
1450}
1451
1452void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1453 SkDEBUGCODE(this->validate();)
1454 if (dst == NULL) {
1455 dst = (SkPath*)this;
1456 }
1457
tomhudson@google.com8d430182011-06-06 19:11:19 +00001458 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459 SkPath tmp;
1460 tmp.fFillType = fFillType;
1461
1462 SkPath::Iter iter(*this, false);
1463 SkPoint pts[4];
1464 SkPath::Verb verb;
1465
reed@google.com4a3b7142012-05-16 17:16:46 +00001466 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 switch (verb) {
1468 case kMove_Verb:
1469 tmp.moveTo(pts[0]);
1470 break;
1471 case kLine_Verb:
1472 tmp.lineTo(pts[1]);
1473 break;
1474 case kQuad_Verb:
1475 subdivide_quad_to(&tmp, pts);
1476 break;
1477 case kCubic_Verb:
1478 subdivide_cubic_to(&tmp, pts);
1479 break;
1480 case kClose_Verb:
1481 tmp.close();
1482 break;
1483 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001484 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 break;
1486 }
1487 }
1488
1489 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001490 SkPathRef::Editor ed(&dst->fPathRef);
1491 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001492 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001494 /*
1495 * If we're not in perspective, we can transform all of the points at
1496 * once.
1497 *
1498 * Here we also want to optimize bounds, by noting if the bounds are
1499 * already known, and if so, we just transform those as well and mark
1500 * them as "known", rather than force the transformed path to have to
1501 * recompute them.
1502 *
1503 * Special gotchas if the path is effectively empty (<= 1 point) or
1504 * if it is non-finite. In those cases bounds need to stay empty,
1505 * regardless of the matrix.
1506 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001508 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001509 if (fIsFinite) {
1510 matrix.mapRect(&dst->fBounds, fBounds);
1511 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1512 dst->fBounds.setEmpty();
1513 }
1514 } else {
1515 dst->fIsFinite = false;
1516 dst->fBounds.setEmpty();
1517 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001519 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001520 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 }
1522
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1524
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001527 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001528 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001530
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001531 if (kUnknown_Direction == fDirection) {
1532 dst->fDirection = kUnknown_Direction;
1533 } else {
1534 SkScalar det2x2 =
1535 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1536 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1537 if (det2x2 < 0) {
1538 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1539 } else if (det2x2 > 0) {
1540 dst->fDirection = fDirection;
1541 } else {
1542 dst->fDirection = kUnknown_Direction;
1543 }
1544 }
1545
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001546 // It's an oval only if it stays a rect.
1547 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001548
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 SkDEBUGCODE(dst->validate();)
1550 }
1551}
1552
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553///////////////////////////////////////////////////////////////////////////////
1554///////////////////////////////////////////////////////////////////////////////
1555
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001556enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001557 kEmptyContour_SegmentState, // The current contour is empty. We may be
1558 // starting processing or we may have just
1559 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001560 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1561 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1562 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563};
1564
1565SkPath::Iter::Iter() {
1566#ifdef SK_DEBUG
1567 fPts = NULL;
1568 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001569 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001570 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571#endif
1572 // need to init enough to make next() harmlessly return kDone_Verb
1573 fVerbs = NULL;
1574 fVerbStop = NULL;
1575 fNeedClose = false;
1576}
1577
1578SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1579 this->setPath(path, forceClose);
1580}
1581
1582void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001583 fPts = path.fPathRef->points();
1584 fVerbs = path.fPathRef->verbs();
1585 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001586 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001587 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 fForceClose = SkToU8(forceClose);
1589 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001590 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591}
1592
1593bool SkPath::Iter::isClosedContour() const {
1594 if (fVerbs == NULL || fVerbs == fVerbStop) {
1595 return false;
1596 }
1597 if (fForceClose) {
1598 return true;
1599 }
1600
1601 const uint8_t* verbs = fVerbs;
1602 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001603
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001604 if (kMove_Verb == *(verbs - 1)) {
1605 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 }
1607
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001608 while (verbs > stop) {
1609 // verbs points one beyond the current verb, decrement first.
1610 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 if (kMove_Verb == v) {
1612 break;
1613 }
1614 if (kClose_Verb == v) {
1615 return true;
1616 }
1617 }
1618 return false;
1619}
1620
1621SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001622 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001624 // A special case: if both points are NaN, SkPoint::operation== returns
1625 // false, but the iterator expects that they are treated as the same.
1626 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001627 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1628 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001629 return kClose_Verb;
1630 }
1631
reed@google.com9e25dbf2012-05-15 17:05:38 +00001632 pts[0] = fLastPt;
1633 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634 fLastPt = fMoveTo;
1635 fCloseLine = true;
1636 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001637 } else {
1638 pts[0] = fMoveTo;
1639 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641}
1642
reed@google.com9e25dbf2012-05-15 17:05:38 +00001643const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001644 if (fSegmentState == kAfterMove_SegmentState) {
1645 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001646 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001647 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001649 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1650 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001651 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653}
1654
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001655void SkPath::Iter::consumeDegenerateSegments() {
1656 // We need to step over anything that will not move the current draw point
1657 // forward before the next move is seen
1658 const uint8_t* lastMoveVerb = 0;
1659 const SkPoint* lastMovePt = 0;
1660 SkPoint lastPt = fLastPt;
1661 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001663 switch (verb) {
1664 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001665 // Keep a record of this most recent move
1666 lastMoveVerb = fVerbs;
1667 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001668 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001669 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001670 fPts++;
1671 break;
1672
1673 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001674 // A close when we are in a segment is always valid except when it
1675 // follows a move which follows a segment.
1676 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001677 return;
1678 }
1679 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001680 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001681 break;
1682
1683 case kLine_Verb:
1684 if (!IsLineDegenerate(lastPt, fPts[0])) {
1685 if (lastMoveVerb) {
1686 fVerbs = lastMoveVerb;
1687 fPts = lastMovePt;
1688 return;
1689 }
1690 return;
1691 }
1692 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001694 fPts++;
1695 break;
1696
1697 case kQuad_Verb:
1698 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1699 if (lastMoveVerb) {
1700 fVerbs = lastMoveVerb;
1701 fPts = lastMovePt;
1702 return;
1703 }
1704 return;
1705 }
1706 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001707 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 fPts += 2;
1709 break;
1710
1711 case kCubic_Verb:
1712 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1713 if (lastMoveVerb) {
1714 fVerbs = lastMoveVerb;
1715 fPts = lastMovePt;
1716 return;
1717 }
1718 return;
1719 }
1720 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001721 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001722 fPts += 3;
1723 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001724
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001726 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727 }
1728 }
1729}
1730
reed@google.com4a3b7142012-05-16 17:16:46 +00001731SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001732 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001733
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735 // Close the curve if requested and if there is some curve to close
1736 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001737 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 return kLine_Verb;
1739 }
1740 fNeedClose = false;
1741 return kClose_Verb;
1742 }
1743 return kDone_Verb;
1744 }
1745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001746 // fVerbs is one beyond the current verb, decrement first
1747 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001748 const SkPoint* SK_RESTRICT srcPts = fPts;
1749 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750
1751 switch (verb) {
1752 case kMove_Verb:
1753 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001754 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 verb = this->autoClose(pts);
1756 if (verb == kClose_Verb) {
1757 fNeedClose = false;
1758 }
1759 return (Verb)verb;
1760 }
1761 if (fVerbs == fVerbStop) { // might be a trailing moveto
1762 return kDone_Verb;
1763 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001764 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001765 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001767 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001768 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 fNeedClose = fForceClose;
1770 break;
1771 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001772 pts[0] = this->cons_moveTo();
1773 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 fLastPt = srcPts[0];
1775 fCloseLine = false;
1776 srcPts += 1;
1777 break;
1778 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001779 pts[0] = this->cons_moveTo();
1780 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 fLastPt = srcPts[1];
1782 srcPts += 2;
1783 break;
1784 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001785 pts[0] = this->cons_moveTo();
1786 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 fLastPt = srcPts[2];
1788 srcPts += 3;
1789 break;
1790 case kClose_Verb:
1791 verb = this->autoClose(pts);
1792 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001793 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 } else {
1795 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001796 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 break;
1800 }
1801 fPts = srcPts;
1802 return (Verb)verb;
1803}
1804
1805///////////////////////////////////////////////////////////////////////////////
1806
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001807SkPath::RawIter::RawIter() {
1808#ifdef SK_DEBUG
1809 fPts = NULL;
1810 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1811#endif
1812 // need to init enough to make next() harmlessly return kDone_Verb
1813 fVerbs = NULL;
1814 fVerbStop = NULL;
1815}
1816
1817SkPath::RawIter::RawIter(const SkPath& path) {
1818 this->setPath(path);
1819}
1820
1821void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001822 fPts = path.fPathRef->points();
1823 fVerbs = path.fPathRef->verbs();
1824 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001825 fMoveTo.fX = fMoveTo.fY = 0;
1826 fLastPt.fX = fLastPt.fY = 0;
1827}
1828
1829SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001830 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001831 if (fVerbs == fVerbStop) {
1832 return kDone_Verb;
1833 }
1834
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001835 // fVerbs points one beyond next verb so decrement first.
1836 unsigned verb = *(--fVerbs);
1837 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001838
1839 switch (verb) {
1840 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001841 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001842 fMoveTo = srcPts[0];
1843 fLastPt = fMoveTo;
1844 srcPts += 1;
1845 break;
1846 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001847 pts[0] = fLastPt;
1848 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001849 fLastPt = srcPts[0];
1850 srcPts += 1;
1851 break;
1852 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001853 pts[0] = fLastPt;
1854 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001855 fLastPt = srcPts[1];
1856 srcPts += 2;
1857 break;
1858 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001859 pts[0] = fLastPt;
1860 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001861 fLastPt = srcPts[2];
1862 srcPts += 3;
1863 break;
1864 case kClose_Verb:
1865 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001866 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001867 break;
1868 }
1869 fPts = srcPts;
1870 return (Verb)verb;
1871}
1872
1873///////////////////////////////////////////////////////////////////////////////
1874
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001876 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877*/
1878
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001879uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 SkDEBUGCODE(this->validate();)
1881
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001882 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 const int byteCount = sizeof(int32_t)
1884#if NEW_PICTURE_FORMAT
1885 + fPathRef->writeSize()
1886#else
1887 + 2 * sizeof(int32_t)
1888 + sizeof(SkPoint) * fPathRef->countPoints()
1889 + sizeof(uint8_t) * fPathRef->countVerbs()
1890#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001891 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001892 return SkAlign4(byteCount);
1893 }
1894
1895 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001896#if !NEW_PICTURE_FORMAT
1897 buffer.write32(fPathRef->countPoints());
1898 buffer.write32(fPathRef->countVerbs());
1899#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001900
1901 // Call getBounds() to ensure (as a side-effect) that fBounds
1902 // and fIsFinite are computed.
1903 const SkRect& bounds = this->getBounds();
1904 SkASSERT(!fBoundsIsDirty);
1905
1906 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
1907 ((fIsOval & 1) << kIsOval_SerializationShift) |
1908 (fConvexity << kConvexity_SerializationShift) |
1909 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001910 (fSegmentMask << kSegmentMask_SerializationShift) |
1911 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001912
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001913 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001914
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001915 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001916
1917 buffer.write(&bounds, sizeof(bounds));
1918
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001919 buffer.padToAlign4();
1920 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921}
1922
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001923uint32_t SkPath::readFromMemory(const void* storage) {
1924 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925#if !NEW_PICTURE_FORMAT
1926 int32_t pcount = buffer.readS32();
1927 int32_t vcount = buffer.readS32();
1928#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001929
reed@google.com98b11f12011-09-21 18:40:27 +00001930 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001931 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
1932 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
1933 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1934 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001935 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
1936 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001937
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001938#if NEW_PICTURE_FORMAT
1939 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
1940#else
1941 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
1942#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001943
1944 buffer.read(&fBounds, sizeof(fBounds));
1945 fBoundsIsDirty = false;
1946
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001947 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001948
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001949 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950
1951 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001952 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953}
1954
1955///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957void SkPath::dump(bool forceClose, const char title[]) const {
1958 Iter iter(*this, forceClose);
1959 SkPoint pts[4];
1960 Verb verb;
1961
1962 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1963 title ? title : "");
1964
reed@google.com4a3b7142012-05-16 17:16:46 +00001965 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 switch (verb) {
1967 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 SkDebugf(" path: moveTo [%g %g]\n",
1969 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 break;
1971 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 SkDebugf(" path: lineTo [%g %g]\n",
1973 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 break;
1975 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1977 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1978 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979 break;
1980 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1982 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1983 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1984 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 break;
1986 case kClose_Verb:
1987 SkDebugf(" path: close\n");
1988 break;
1989 default:
1990 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1991 verb = kDone_Verb; // stop the loop
1992 break;
1993 }
1994 }
1995 SkDebugf("path: done %s\n", title ? title : "");
1996}
1997
reed@android.come522ca52009-11-23 20:10:41 +00001998void SkPath::dump() const {
1999 this->dump(false);
2000}
2001
2002#ifdef SK_DEBUG
2003void SkPath::validate() const {
2004 SkASSERT(this != NULL);
2005 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002006
djsollen@google.com077348c2012-10-22 20:23:32 +00002007#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002008 if (!fBoundsIsDirty) {
2009 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002010
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002011 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002012 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002013
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002014 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002015 // if we're empty, fBounds may be empty but translated, so we can't
2016 // necessarily compare to bounds directly
2017 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2018 // be [2, 2, 2, 2]
2019 SkASSERT(bounds.isEmpty());
2020 SkASSERT(fBounds.isEmpty());
2021 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002022 if (bounds.isEmpty()) {
2023 SkASSERT(fBounds.isEmpty());
2024 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002025 if (!fBounds.isEmpty()) {
2026 SkASSERT(fBounds.contains(bounds));
2027 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002028 }
reed@android.come522ca52009-11-23 20:10:41 +00002029 }
2030 }
reed@google.com10296cc2011-09-21 12:29:05 +00002031
2032 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002033 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2034 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2035 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002036 case kLine_Verb:
2037 mask |= kLine_SegmentMask;
2038 break;
2039 case kQuad_Verb:
2040 mask |= kQuad_SegmentMask;
2041 break;
2042 case kCubic_Verb:
2043 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002044 case kMove_Verb: // these verbs aren't included in the segment mask.
2045 case kClose_Verb:
2046 break;
2047 case kDone_Verb:
2048 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2049 break;
2050 default:
2051 SkDEBUGFAIL("Unknown Verb");
2052 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002053 }
2054 }
2055 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002056#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002057}
djsollen@google.com077348c2012-10-22 20:23:32 +00002058#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002059
reed@google.com04863fa2011-05-15 04:08:24 +00002060///////////////////////////////////////////////////////////////////////////////
2061
reed@google.com0b7b9822011-05-16 12:29:27 +00002062static int sign(SkScalar x) { return x < 0; }
2063#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002064
reed@google.com04863fa2011-05-15 04:08:24 +00002065static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002066 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002067}
2068
2069// only valid for a single contour
2070struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002071 Convexicator()
2072 : fPtCount(0)
2073 , fConvexity(SkPath::kConvex_Convexity)
2074 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002075 fSign = 0;
2076 // warnings
2077 fCurrPt.set(0, 0);
2078 fVec0.set(0, 0);
2079 fVec1.set(0, 0);
2080 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002081
2082 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002083 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002084 }
2085
2086 SkPath::Convexity getConvexity() const { return fConvexity; }
2087
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002088 /** The direction returned is only valid if the path is determined convex */
2089 SkPath::Direction getDirection() const { return fDirection; }
2090
reed@google.com04863fa2011-05-15 04:08:24 +00002091 void addPt(const SkPoint& pt) {
2092 if (SkPath::kConcave_Convexity == fConvexity) {
2093 return;
2094 }
2095
2096 if (0 == fPtCount) {
2097 fCurrPt = pt;
2098 ++fPtCount;
2099 } else {
2100 SkVector vec = pt - fCurrPt;
2101 if (vec.fX || vec.fY) {
2102 fCurrPt = pt;
2103 if (++fPtCount == 2) {
2104 fFirstVec = fVec1 = vec;
2105 } else {
2106 SkASSERT(fPtCount > 2);
2107 this->addVec(vec);
2108 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002109
reed@google.com85b6e392011-05-15 20:25:17 +00002110 int sx = sign(vec.fX);
2111 int sy = sign(vec.fY);
2112 fDx += (sx != fSx);
2113 fDy += (sy != fSy);
2114 fSx = sx;
2115 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002116
reed@google.com85b6e392011-05-15 20:25:17 +00002117 if (fDx > 3 || fDy > 3) {
2118 fConvexity = SkPath::kConcave_Convexity;
2119 }
reed@google.com04863fa2011-05-15 04:08:24 +00002120 }
2121 }
2122 }
2123
2124 void close() {
2125 if (fPtCount > 2) {
2126 this->addVec(fFirstVec);
2127 }
2128 }
2129
2130private:
2131 void addVec(const SkVector& vec) {
2132 SkASSERT(vec.fX || vec.fY);
2133 fVec0 = fVec1;
2134 fVec1 = vec;
2135 int sign = CrossProductSign(fVec0, fVec1);
2136 if (0 == fSign) {
2137 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002138 if (1 == sign) {
2139 fDirection = SkPath::kCW_Direction;
2140 } else if (-1 == sign) {
2141 fDirection = SkPath::kCCW_Direction;
2142 }
reed@google.com04863fa2011-05-15 04:08:24 +00002143 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002144 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002145 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002146 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002147 }
2148 }
2149 }
2150
2151 SkPoint fCurrPt;
2152 SkVector fVec0, fVec1, fFirstVec;
2153 int fPtCount; // non-degenerate points
2154 int fSign;
2155 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002156 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002157 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002158};
2159
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002160SkPath::Convexity SkPath::internalGetConvexity() const {
2161 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002162 SkPoint pts[4];
2163 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002164 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002165
2166 int contourCount = 0;
2167 int count;
2168 Convexicator state;
2169
2170 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2171 switch (verb) {
2172 case kMove_Verb:
2173 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002174 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002175 return kConcave_Convexity;
2176 }
2177 pts[1] = pts[0];
2178 count = 1;
2179 break;
2180 case kLine_Verb: count = 1; break;
2181 case kQuad_Verb: count = 2; break;
2182 case kCubic_Verb: count = 3; break;
2183 case kClose_Verb:
2184 state.close();
2185 count = 0;
2186 break;
2187 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002188 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002189 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002190 return kConcave_Convexity;
2191 }
2192
2193 for (int i = 1; i <= count; i++) {
2194 state.addPt(pts[i]);
2195 }
2196 // early exit
2197 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002198 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002199 return kConcave_Convexity;
2200 }
2201 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002202 fConvexity = state.getConvexity();
2203 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2204 fDirection = state.getDirection();
2205 }
2206 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002207}
reed@google.com69a99432012-01-10 18:00:10 +00002208
2209///////////////////////////////////////////////////////////////////////////////
2210
2211class ContourIter {
2212public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002213 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002214
2215 bool done() const { return fDone; }
2216 // if !done() then these may be called
2217 int count() const { return fCurrPtCount; }
2218 const SkPoint* pts() const { return fCurrPt; }
2219 void next();
2220
2221private:
2222 int fCurrPtCount;
2223 const SkPoint* fCurrPt;
2224 const uint8_t* fCurrVerb;
2225 const uint8_t* fStopVerbs;
2226 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002227 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002228};
2229
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002230ContourIter::ContourIter(const SkPathRef& pathRef) {
2231 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002232 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002233 fCurrPt = pathRef.points();
2234 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002235 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002236 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002237 this->next();
2238}
2239
2240void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002241 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002242 fDone = true;
2243 }
2244 if (fDone) {
2245 return;
2246 }
2247
2248 // skip pts of prev contour
2249 fCurrPt += fCurrPtCount;
2250
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002251 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002252 int ptCount = 1; // moveTo
2253 const uint8_t* verbs = fCurrVerb;
2254
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002255 for (--verbs; verbs > fStopVerbs; --verbs) {
2256 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002257 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002258 goto CONTOUR_END;
2259 case SkPath::kLine_Verb:
2260 ptCount += 1;
2261 break;
2262 case SkPath::kQuad_Verb:
2263 ptCount += 2;
2264 break;
2265 case SkPath::kCubic_Verb:
2266 ptCount += 3;
2267 break;
2268 default: // kClose_Verb, just keep going
2269 break;
2270 }
2271 }
2272CONTOUR_END:
2273 fCurrPtCount = ptCount;
2274 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002275 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002276}
2277
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002278// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002279static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002280 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2281 // We may get 0 when the above subtracts underflow. We expect this to be
2282 // very rare and lazily promote to double.
2283 if (0 == cross) {
2284 double p0x = SkScalarToDouble(p0.fX);
2285 double p0y = SkScalarToDouble(p0.fY);
2286
2287 double p1x = SkScalarToDouble(p1.fX);
2288 double p1y = SkScalarToDouble(p1.fY);
2289
2290 double p2x = SkScalarToDouble(p2.fX);
2291 double p2y = SkScalarToDouble(p2.fY);
2292
2293 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2294 (p1y - p0y) * (p2x - p0x));
2295
2296 }
2297 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002298}
2299
reed@google.comc1ea60a2012-01-31 15:15:36 +00002300// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002301static int find_max_y(const SkPoint pts[], int count) {
2302 SkASSERT(count > 0);
2303 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002304 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002305 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002306 SkScalar y = pts[i].fY;
2307 if (y > max) {
2308 max = y;
2309 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002310 }
2311 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002312 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002313}
2314
reed@google.comcabaf1d2012-01-11 21:03:05 +00002315static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2316 int i = index;
2317 for (;;) {
2318 i = (i + inc) % n;
2319 if (i == index) { // we wrapped around, so abort
2320 break;
2321 }
2322 if (pts[index] != pts[i]) { // found a different point, success!
2323 break;
2324 }
2325 }
2326 return i;
2327}
2328
reed@google.comc1ea60a2012-01-31 15:15:36 +00002329/**
2330 * Starting at index, and moving forward (incrementing), find the xmin and
2331 * xmax of the contiguous points that have the same Y.
2332 */
2333static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2334 int* maxIndexPtr) {
2335 const SkScalar y = pts[index].fY;
2336 SkScalar min = pts[index].fX;
2337 SkScalar max = min;
2338 int minIndex = index;
2339 int maxIndex = index;
2340 for (int i = index + 1; i < n; ++i) {
2341 if (pts[i].fY != y) {
2342 break;
2343 }
2344 SkScalar x = pts[i].fX;
2345 if (x < min) {
2346 min = x;
2347 minIndex = i;
2348 } else if (x > max) {
2349 max = x;
2350 maxIndex = i;
2351 }
2352 }
2353 *maxIndexPtr = maxIndex;
2354 return minIndex;
2355}
2356
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002358 if (dir) {
2359 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2360 }
reed@google.comac8543f2012-01-30 20:51:25 +00002361}
2362
reed@google.comc1ea60a2012-01-31 15:15:36 +00002363#if 0
2364#include "SkString.h"
2365#include "../utils/SkParsePath.h"
2366static void dumpPath(const SkPath& path) {
2367 SkString str;
2368 SkParsePath::ToSVGString(path, &str);
2369 SkDebugf("%s\n", str.c_str());
2370}
2371#endif
2372
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002373namespace {
2374// for use with convex_dir_test
2375double mul(double a, double b) { return a * b; }
2376SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2377double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2378SkScalar toScalar(SkScalar a) { return a; }
2379
2380// determines the winding direction of a convex polygon with the precision
2381// of T. CAST_SCALAR casts an SkScalar to T.
2382template <typename T, T (CAST_SCALAR)(SkScalar)>
2383bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2384 // we find the first three points that form a non-degenerate
2385 // triangle. If there are no such points then the path is
2386 // degenerate. The first is always point 0. Now we find the second
2387 // point.
2388 int i = 0;
2389 enum { kX = 0, kY = 1 };
2390 T v0[2];
2391 while (1) {
2392 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2393 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2394 if (v0[kX] || v0[kY]) {
2395 break;
2396 }
2397 if (++i == n - 1) {
2398 return false;
2399 }
2400 }
2401 // now find a third point that is not colinear with the first two
2402 // points and check the orientation of the triangle (which will be
2403 // the same as the orientation of the path).
2404 for (++i; i < n; ++i) {
2405 T v1[2];
2406 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2407 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2408 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2409 if (0 != cross) {
2410 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2411 return true;
2412 }
2413 }
2414 return false;
2415}
2416}
2417
reed@google.comac8543f2012-01-30 20:51:25 +00002418/*
2419 * We loop through all contours, and keep the computed cross-product of the
2420 * contour that contained the global y-max. If we just look at the first
2421 * contour, we may find one that is wound the opposite way (correctly) since
2422 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2423 * that is outer most (or at least has the global y-max) before we can consider
2424 * its cross product.
2425 */
reed@google.com69a99432012-01-10 18:00:10 +00002426bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002427// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002428 // don't want to pay the cost for computing this if it
2429 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002430
2431 if (kUnknown_Direction != fDirection) {
2432 *dir = static_cast<Direction>(fDirection);
2433 return true;
2434 }
reed@google.com69a99432012-01-10 18:00:10 +00002435 const Convexity conv = this->getConvexityOrUnknown();
2436
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002437 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002438
reed@google.comac8543f2012-01-30 20:51:25 +00002439 // initialize with our logical y-min
2440 SkScalar ymax = this->getBounds().fTop;
2441 SkScalar ymaxCross = 0;
2442
reed@google.com69a99432012-01-10 18:00:10 +00002443 for (; !iter.done(); iter.next()) {
2444 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002445 if (n < 3) {
2446 continue;
2447 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002448
reed@google.comcabaf1d2012-01-11 21:03:05 +00002449 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002450 SkScalar cross = 0;
2451 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002452 // We try first at scalar precision, and then again at double
2453 // precision. This is because the vectors computed between distant
2454 // points may lose too much precision.
2455 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002456 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002457 return true;
2458 }
2459 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002461 return true;
2462 } else {
2463 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002464 }
2465 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002466 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002467 if (pts[index].fY < ymax) {
2468 continue;
2469 }
2470
reed@google.comc1ea60a2012-01-31 15:15:36 +00002471 // If there is more than 1 distinct point at the y-max, we take the
2472 // x-min and x-max of them and just subtract to compute the dir.
2473 if (pts[(index + 1) % n].fY == pts[index].fY) {
2474 int maxIndex;
2475 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002476 if (minIndex == maxIndex) {
2477 goto TRY_CROSSPROD;
2478 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002479 SkASSERT(pts[minIndex].fY == pts[index].fY);
2480 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2481 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2482 // we just subtract the indices, and let that auto-convert to
2483 // SkScalar, since we just want - or + to signal the direction.
2484 cross = minIndex - maxIndex;
2485 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002486 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002487 // Find a next and prev index to use for the cross-product test,
2488 // but we try to find pts that form non-zero vectors from pts[index]
2489 //
2490 // Its possible that we can't find two non-degenerate vectors, so
2491 // we have to guard our search (e.g. all the pts could be in the
2492 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002493
reed@google.comc1ea60a2012-01-31 15:15:36 +00002494 // we pass n - 1 instead of -1 so we don't foul up % operator by
2495 // passing it a negative LH argument.
2496 int prev = find_diff_pt(pts, index, n, n - 1);
2497 if (prev == index) {
2498 // completely degenerate, skip to next contour
2499 continue;
2500 }
2501 int next = find_diff_pt(pts, index, n, 1);
2502 SkASSERT(next != index);
2503 cross = cross_prod(pts[prev], pts[index], pts[next]);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002504 // if we get a zero and the points are horizontal, then we look at the spread in
2505 // x-direction. We really should continue to walk away from the degeneracy until
2506 // there is a divergence.
2507 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002508 // construct the subtract so we get the correct Direction below
2509 cross = pts[index].fX - pts[next].fX;
2510 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002511 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002512
reed@google.comac8543f2012-01-30 20:51:25 +00002513 if (cross) {
2514 // record our best guess so far
2515 ymax = pts[index].fY;
2516 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002517 }
reed@google.com69a99432012-01-10 18:00:10 +00002518 }
2519 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002520 if (ymaxCross) {
2521 crossToDir(ymaxCross, dir);
2522 fDirection = *dir;
2523 return true;
2524 } else {
2525 return false;
2526 }
reed@google.comac8543f2012-01-30 20:51:25 +00002527}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002528
2529///////////////////////////////////////////////////////////////////////////////
2530
2531static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2532 SkScalar D, SkScalar t) {
2533 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2534}
2535
2536static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2537 SkScalar t) {
2538 SkScalar A = c3 + 3*(c1 - c2) - c0;
2539 SkScalar B = 3*(c2 - c1 - c1 + c0);
2540 SkScalar C = 3*(c1 - c0);
2541 SkScalar D = c0;
2542 return eval_cubic_coeff(A, B, C, D, t);
2543}
2544
2545/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2546 t value such that cubic(t) = target
2547 */
2548static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2549 SkScalar target, SkScalar* t) {
2550 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2551 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002552
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002553 SkScalar D = c0 - target;
2554 SkScalar A = c3 + 3*(c1 - c2) - c0;
2555 SkScalar B = 3*(c2 - c1 - c1 + c0);
2556 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002557
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002558 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2559 SkScalar minT = 0;
2560 SkScalar maxT = SK_Scalar1;
2561 SkScalar mid;
2562 int i;
2563 for (i = 0; i < 16; i++) {
2564 mid = SkScalarAve(minT, maxT);
2565 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2566 if (delta < 0) {
2567 minT = mid;
2568 delta = -delta;
2569 } else {
2570 maxT = mid;
2571 }
2572 if (delta < TOLERANCE) {
2573 break;
2574 }
2575 }
2576 *t = mid;
2577 return true;
2578}
2579
2580template <size_t N> static void find_minmax(const SkPoint pts[],
2581 SkScalar* minPtr, SkScalar* maxPtr) {
2582 SkScalar min, max;
2583 min = max = pts[0].fX;
2584 for (size_t i = 1; i < N; ++i) {
2585 min = SkMinScalar(min, pts[i].fX);
2586 max = SkMaxScalar(max, pts[i].fX);
2587 }
2588 *minPtr = min;
2589 *maxPtr = max;
2590}
2591
2592static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2593 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002594
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002595 int dir = 1;
2596 if (pts[0].fY > pts[3].fY) {
2597 storage[0] = pts[3];
2598 storage[1] = pts[2];
2599 storage[2] = pts[1];
2600 storage[3] = pts[0];
2601 pts = storage;
2602 dir = -1;
2603 }
2604 if (y < pts[0].fY || y >= pts[3].fY) {
2605 return 0;
2606 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002607
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002608 // quickreject or quickaccept
2609 SkScalar min, max;
2610 find_minmax<4>(pts, &min, &max);
2611 if (x < min) {
2612 return 0;
2613 }
2614 if (x > max) {
2615 return dir;
2616 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002617
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002618 // compute the actual x(t) value
2619 SkScalar t, xt;
2620 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2621 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2622 } else {
2623 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2624 xt = y < mid ? pts[0].fX : pts[3].fX;
2625 }
2626 return xt < x ? dir : 0;
2627}
2628
2629static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2630 SkPoint dst[10];
2631 int n = SkChopCubicAtYExtrema(pts, dst);
2632 int w = 0;
2633 for (int i = 0; i <= n; ++i) {
2634 w += winding_mono_cubic(&dst[i * 3], x, y);
2635 }
2636 return w;
2637}
2638
2639static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2640 SkScalar y0 = pts[0].fY;
2641 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002642
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002643 int dir = 1;
2644 if (y0 > y2) {
2645 SkTSwap(y0, y2);
2646 dir = -1;
2647 }
2648 if (y < y0 || y >= y2) {
2649 return 0;
2650 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002651
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002652 // bounds check on X (not required. is it faster?)
2653#if 0
2654 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2655 return 0;
2656 }
2657#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002658
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002659 SkScalar roots[2];
2660 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2661 2 * (pts[1].fY - pts[0].fY),
2662 pts[0].fY - y,
2663 roots);
2664 SkASSERT(n <= 1);
2665 SkScalar xt;
2666 if (0 == n) {
2667 SkScalar mid = SkScalarAve(y0, y2);
2668 // Need [0] and [2] if dir == 1
2669 // and [2] and [0] if dir == -1
2670 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2671 } else {
2672 SkScalar t = roots[0];
2673 SkScalar C = pts[0].fX;
2674 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2675 SkScalar B = 2 * (pts[1].fX - C);
2676 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2677 }
2678 return xt < x ? dir : 0;
2679}
2680
2681static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2682 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2683 if (y0 == y1) {
2684 return true;
2685 }
2686 if (y0 < y1) {
2687 return y1 <= y2;
2688 } else {
2689 return y1 >= y2;
2690 }
2691}
2692
2693static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2694 SkPoint dst[5];
2695 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002696
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002697 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2698 n = SkChopQuadAtYExtrema(pts, dst);
2699 pts = dst;
2700 }
2701 int w = winding_mono_quad(pts, x, y);
2702 if (n > 0) {
2703 w += winding_mono_quad(&pts[2], x, y);
2704 }
2705 return w;
2706}
2707
2708static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2709 SkScalar x0 = pts[0].fX;
2710 SkScalar y0 = pts[0].fY;
2711 SkScalar x1 = pts[1].fX;
2712 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002713
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002715
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002716 int dir = 1;
2717 if (y0 > y1) {
2718 SkTSwap(y0, y1);
2719 dir = -1;
2720 }
2721 if (y < y0 || y >= y1) {
2722 return 0;
2723 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002724
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002725 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2726 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 if (SkScalarSignAsInt(cross) == dir) {
2729 dir = 0;
2730 }
2731 return dir;
2732}
2733
2734bool SkPath::contains(SkScalar x, SkScalar y) const {
2735 bool isInverse = this->isInverseFillType();
2736 if (this->isEmpty()) {
2737 return isInverse;
2738 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002739
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002740 const SkRect& bounds = this->getBounds();
2741 if (!bounds.contains(x, y)) {
2742 return isInverse;
2743 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002744
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002745 SkPath::Iter iter(*this, true);
2746 bool done = false;
2747 int w = 0;
2748 do {
2749 SkPoint pts[4];
2750 switch (iter.next(pts, false)) {
2751 case SkPath::kMove_Verb:
2752 case SkPath::kClose_Verb:
2753 break;
2754 case SkPath::kLine_Verb:
2755 w += winding_line(pts, x, y);
2756 break;
2757 case SkPath::kQuad_Verb:
2758 w += winding_quad(pts, x, y);
2759 break;
2760 case SkPath::kCubic_Verb:
2761 w += winding_cubic(pts, x, y);
2762 break;
2763 case SkPath::kDone_Verb:
2764 done = true;
2765 break;
2766 }
2767 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 switch (this->getFillType()) {
2770 case SkPath::kEvenOdd_FillType:
2771 case SkPath::kInverseEvenOdd_FillType:
2772 w &= 1;
2773 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002774 default:
2775 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776 }
2777 return SkToBool(w);
2778}
2779