blob: ef872dc39aff78160a55fc4e2a5178903b6112b3 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
80 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
85 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000102 fPath->fBounds = fRect;
103 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000104 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000106 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000107 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000108 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 }
110 }
reed@google.comabf15c12011-01-18 20:35:51 +0000111
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000113 SkPath* fPath;
114 SkRect fRect;
115 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000116 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000117 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000120 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000122 // Mark the path's bounds as dirty if (1) they are, or (2) the path
123 // is non-finite, and therefore its bounds are not meaningful
124 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000127 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000128 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 }
130};
131
reed@google.com0bb18bb2012-07-26 15:20:36 +0000132// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000133static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
134 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000135 if (count <= 1) { // we ignore just 1 point (moveto)
136 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000137 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000139 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 }
141}
142
143////////////////////////////////////////////////////////////////////////////
144
145/*
146 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000147 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
149
150 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000151 1. If we encounter degenerate segments, remove them
152 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
153 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
154 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155*/
156
157////////////////////////////////////////////////////////////////////////////
158
reed@google.comd335d1d2012-01-12 18:17:11 +0000159// flag to require a moveTo if we begin with something else, like lineTo etc.
160#define INITIAL_LASTMOVETOINDEX_VALUE ~0
161
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000162SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000163 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164#ifdef SK_BUILD_FOR_ANDROID
165 , fGenerationID(0)
166#endif
167{
168 this->resetFields();
169}
170
171void SkPath::resetFields() {
172 //fPathRef is assumed to have been emptied by the caller.
173 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
174 fFillType = kWinding_FillType;
175 fSegmentMask = 0;
176 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000177 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000178 fDirection = kUnknown_Direction;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fIsFinite = false;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000180 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000181#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 GEN_ID_INC;
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000183 // We don't touch fSourcePath. It's used to track texture garbage collection, so we don't
184 // want to muck with it if it's been set to something non-NULL.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000185#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000186}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000189 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000191#ifdef SK_BUILD_FOR_ANDROID
192 fGenerationID = that.fGenerationID;
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000193 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000194#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196}
197
198SkPath::~SkPath() {
199 SkDEBUGCODE(this->validate();)
200}
201
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202SkPath& SkPath::operator=(const SkPath& that) {
203 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205 if (this != &that) {
206 fPathRef.reset(SkRef(that.fPathRef.get()));
207 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000208#ifdef SK_BUILD_FOR_ANDROID
209 GEN_ID_INC; // Similar to swap, we can't just copy this or it could go back in time.
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000210 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000211#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213 SkDEBUGCODE(this->validate();)
214 return *this;
215}
216
bungeman@google.coma5809a32013-06-21 15:13:34 +0000217void SkPath::copyFields(const SkPath& that) {
218 //fPathRef is assumed to have been set by the caller.
219 fBounds = that.fBounds;
220 fLastMoveToIndex = that.fLastMoveToIndex;
221 fFillType = that.fFillType;
222 fSegmentMask = that.fSegmentMask;
223 fBoundsIsDirty = that.fBoundsIsDirty;
224 fConvexity = that.fConvexity;
225 fDirection = that.fDirection;
226 fIsFinite = that.fIsFinite;
227 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228}
229
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000230bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000231 // note: don't need to look at isConvex or bounds, since just comparing the
232 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000233
234 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
235 // since it is only a cache of info in the fVerbs, but its a fast way to
236 // notice a difference
237
reed@android.com3abec1d2009-03-02 05:36:20 +0000238 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000239 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000240 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000241}
242
bungeman@google.coma5809a32013-06-21 15:13:34 +0000243void SkPath::swap(SkPath& that) {
244 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245
bungeman@google.coma5809a32013-06-21 15:13:34 +0000246 if (this != &that) {
247 fPathRef.swap(&that.fPathRef);
248 SkTSwap<SkRect>(fBounds, that.fBounds);
249 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
250 SkTSwap<uint8_t>(fFillType, that.fFillType);
251 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
252 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
253 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
254 SkTSwap<uint8_t>(fDirection, that.fDirection);
255 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
256 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000257#ifdef SK_BUILD_FOR_ANDROID
258 // It doesn't really make sense to swap the generation IDs here, because they might go
259 // backwards. To be safe we increment both to mark them both as changed.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000260 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000261 GEN_ID_PTR_INC(&that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000262 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
263#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 }
265}
266
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000267static inline bool check_edge_against_rect(const SkPoint& p0,
268 const SkPoint& p1,
269 const SkRect& rect,
270 SkPath::Direction dir) {
271 const SkPoint* edgeBegin;
272 SkVector v;
273 if (SkPath::kCW_Direction == dir) {
274 v = p1 - p0;
275 edgeBegin = &p0;
276 } else {
277 v = p0 - p1;
278 edgeBegin = &p1;
279 }
280 if (v.fX || v.fY) {
281 // check the cross product of v with the vec from edgeBegin to each rect corner
282 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
283 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
284 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
285 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
286 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
287 return false;
288 }
289 }
290 return true;
291}
292
293bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
294 // This only handles non-degenerate convex paths currently.
295 if (kConvex_Convexity != this->getConvexity()) {
296 return false;
297 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000298
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 Direction direction;
300 if (!this->cheapComputeDirection(&direction)) {
301 return false;
302 }
303
304 SkPoint firstPt;
305 SkPoint prevPt;
306 RawIter iter(*this);
307 SkPath::Verb verb;
308 SkPoint pts[4];
309 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000310 SkDEBUGCODE(int segmentCount = 0;)
311 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312
313 while ((verb = iter.next(pts)) != kDone_Verb) {
314 int nextPt = -1;
315 switch (verb) {
316 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 SkDEBUGCODE(++moveCnt);
319 firstPt = prevPt = pts[0];
320 break;
321 case kLine_Verb:
322 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000323 SkASSERT(moveCnt && !closeCount);
324 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000325 break;
326 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000327 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000328 SkASSERT(moveCnt && !closeCount);
329 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000330 nextPt = 2;
331 break;
332 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000333 SkASSERT(moveCnt && !closeCount);
334 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000335 nextPt = 3;
336 break;
337 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000338 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000339 break;
340 default:
341 SkDEBUGFAIL("unknown verb");
342 }
343 if (-1 != nextPt) {
344 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
345 return false;
346 }
347 prevPt = pts[nextPt];
348 }
349 }
350
351 return check_edge_against_rect(prevPt, firstPt, rect, direction);
352}
353
djsollen@google.com56c69772011-11-08 19:00:26 +0000354#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000355uint32_t SkPath::getGenerationID() const {
356 return fGenerationID;
357}
djsollen@google.come63793a2012-03-21 15:39:03 +0000358
359const SkPath* SkPath::getSourcePath() const {
360 return fSourcePath;
361}
362
363void SkPath::setSourcePath(const SkPath* path) {
364 fSourcePath = path;
365}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000366#endif
367
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368void SkPath::reset() {
369 SkDEBUGCODE(this->validate();)
370
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000371 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000372 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373}
374
375void SkPath::rewind() {
376 SkDEBUGCODE(this->validate();)
377
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000378 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000379 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380}
381
382bool SkPath::isEmpty() const {
383 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000384 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385}
386
reed@google.com7e6c4d12012-05-10 14:05:43 +0000387bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000388 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000389
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000390 if (2 == verbCount) {
391 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
392 if (kLine_Verb == fPathRef->atVerb(1)) {
393 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000394 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000395 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000396 line[0] = pts[0];
397 line[1] = pts[1];
398 }
399 return true;
400 }
401 }
402 return false;
403}
404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405/*
406 Determines if path is a rect by keeping track of changes in direction
407 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 The direction is computed such that:
410 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000411 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000412 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000413 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000414
caryclark@google.comf1316942011-07-26 19:54:45 +0000415A rectangle cycles up/right/down/left or up/left/down/right.
416
417The test fails if:
418 The path is closed, and followed by a line.
419 A second move creates a new endpoint.
420 A diagonal line is parsed.
421 There's more than four changes of direction.
422 There's a discontinuity on the line (e.g., a move in the middle)
423 The line reverses direction.
424 The rectangle doesn't complete a cycle.
425 The path contains a quadratic or cubic.
426 The path contains fewer than four points.
427 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000428
caryclark@google.comf1316942011-07-26 19:54:45 +0000429It's OK if the path has:
430 Several colinear line segments composing a rectangle side.
431 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000432
caryclark@google.comf1316942011-07-26 19:54:45 +0000433The direction takes advantage of the corners found since opposite sides
434must travel in opposite directions.
435
436FIXME: Allow colinear quads and cubics to be treated like lines.
437FIXME: If the API passes fill-only, return true if the filled stroke
438 is a rectangle, though the caller failed to close the path.
439 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000440bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
441 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 int corners = 0;
443 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000444 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000445 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000446 first.set(0, 0);
447 last.set(0, 0);
448 int firstDirection = 0;
449 int lastDirection = 0;
450 int nextDirection = 0;
451 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000453 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000454 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
455 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000457 savePts = pts;
458 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 autoClose = true;
460 case kLine_Verb: {
461 SkScalar left = last.fX;
462 SkScalar top = last.fY;
463 SkScalar right = pts->fX;
464 SkScalar bottom = pts->fY;
465 ++pts;
466 if (left != right && top != bottom) {
467 return false; // diagonal
468 }
469 if (left == right && top == bottom) {
470 break; // single point on side OK
471 }
472 nextDirection = (left != right) << 0 |
473 (left < right || top < bottom) << 1;
474 if (0 == corners) {
475 firstDirection = nextDirection;
476 first = last;
477 last = pts[-1];
478 corners = 1;
479 closedOrMoved = false;
480 break;
481 }
482 if (closedOrMoved) {
483 return false; // closed followed by a line
484 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000485 if (autoClose && nextDirection == firstDirection) {
486 break; // colinear with first
487 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 closedOrMoved = autoClose;
489 if (lastDirection != nextDirection) {
490 if (++corners > 4) {
491 return false; // too many direction changes
492 }
493 }
494 last = pts[-1];
495 if (lastDirection == nextDirection) {
496 break; // colinear segment
497 }
498 // Possible values for corners are 2, 3, and 4.
499 // When corners == 3, nextDirection opposes firstDirection.
500 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000501 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
503 if ((directionCycle ^ turn) != nextDirection) {
504 return false; // direction didn't follow cycle
505 }
506 break;
507 }
508 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000509 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 case kCubic_Verb:
511 return false; // quadratic, cubic not allowed
512 case kMove_Verb:
513 last = *pts++;
514 closedOrMoved = true;
515 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000516 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000517 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000518 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000520 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000521 lastDirection = nextDirection;
522 }
523 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000524 bool result = 4 == corners && (first == last || autoClose);
525 if (savePts) {
526 *ptsPtr = savePts;
527 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000528 if (result && isClosed) {
529 *isClosed = autoClose;
530 }
531 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000532 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000533 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 return result;
535}
536
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000537bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000538 SkDEBUGCODE(this->validate();)
539 int currVerb = 0;
540 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000541 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
542 if (result && rect) {
543 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000544 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000545 return result;
546}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000547
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000548bool SkPath::isRect(bool* isClosed, Direction* direction) const {
549 SkDEBUGCODE(this->validate();)
550 int currVerb = 0;
551 const SkPoint* pts = fPathRef->points();
552 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000553}
554
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 SkDEBUGCODE(this->validate();)
557 int currVerb = 0;
558 const SkPoint* pts = fPathRef->points();
559 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000560 Direction testDirs[2];
561 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000562 return false;
563 }
564 const SkPoint* last = pts;
565 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000566 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000567 testRects[0].set(first, SkToS32(last - first));
568 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 if (testRects[0].contains(testRects[1])) {
570 if (rects) {
571 rects[0] = testRects[0];
572 rects[1] = testRects[1];
573 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000574 if (dirs) {
575 dirs[0] = testDirs[0];
576 dirs[1] = testDirs[1];
577 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000578 return true;
579 }
580 if (testRects[1].contains(testRects[0])) {
581 if (rects) {
582 rects[0] = testRects[1];
583 rects[1] = testRects[0];
584 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000585 if (dirs) {
586 dirs[0] = testDirs[1];
587 dirs[1] = testDirs[0];
588 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000589 return true;
590 }
591 }
592 return false;
593}
594
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000595int SkPath::countPoints() const {
596 return fPathRef->countPoints();
597}
598
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000599int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 SkDEBUGCODE(this->validate();)
601
602 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000603 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000604 int count = SkMin32(max, fPathRef->countPoints());
605 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
606 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607}
608
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000609SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
611 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000612 }
613 return SkPoint::Make(0, 0);
614}
615
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000616int SkPath::countVerbs() const {
617 return fPathRef->countVerbs();
618}
619
620static inline void copy_verbs_reverse(uint8_t* inorderDst,
621 const uint8_t* reversedSrc,
622 int count) {
623 for (int i = 0; i < count; ++i) {
624 inorderDst[i] = reversedSrc[~i];
625 }
626}
627
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000628int SkPath::getVerbs(uint8_t dst[], int max) const {
629 SkDEBUGCODE(this->validate();)
630
631 SkASSERT(max >= 0);
632 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 int count = SkMin32(max, fPathRef->countVerbs());
634 copy_verbs_reverse(dst, fPathRef->verbs(), count);
635 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000636}
637
reed@google.com294dd7b2011-10-11 11:58:32 +0000638bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 SkDEBUGCODE(this->validate();)
640
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000641 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000642 if (count > 0) {
643 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000646 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000648 if (lastPt) {
649 lastPt->set(0, 0);
650 }
651 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652}
653
654void SkPath::setLastPt(SkScalar x, SkScalar y) {
655 SkDEBUGCODE(this->validate();)
656
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 if (count == 0) {
659 this->moveTo(x, y);
660 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000661 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
663 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000664 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
666}
667
reed@android.comd252db02009-04-01 18:31:44 +0000668void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000670 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000673 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
reed@google.com04863fa2011-05-15 04:08:24 +0000676void SkPath::setConvexity(Convexity c) {
677 if (fConvexity != c) {
678 fConvexity = c;
679 GEN_ID_INC;
680 }
681}
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683//////////////////////////////////////////////////////////////////////////////
684// Construction methods
685
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000686#define DIRTY_AFTER_EDIT \
687 do { \
688 fBoundsIsDirty = true; \
689 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000690 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000691 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000692 } while (0)
693
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000694#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
695 do { \
696 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000697 } while (0)
698
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699void SkPath::incReserve(U16CPU inc) {
700 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000701 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000702 SkDEBUGCODE(this->validate();)
703}
704
705void SkPath::moveTo(SkScalar x, SkScalar y) {
706 SkDEBUGCODE(this->validate();)
707
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000708 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709
reed@google.comd335d1d2012-01-12 18:17:11 +0000710 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000711 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000713 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000715 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000716 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717}
718
719void SkPath::rMoveTo(SkScalar x, SkScalar y) {
720 SkPoint pt;
721 this->getLastPt(&pt);
722 this->moveTo(pt.fX + x, pt.fY + y);
723}
724
reed@google.comd335d1d2012-01-12 18:17:11 +0000725void SkPath::injectMoveToIfNeeded() {
726 if (fLastMoveToIndex < 0) {
727 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000728 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000729 x = y = 0;
730 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000731 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000732 x = pt.fX;
733 y = pt.fY;
734 }
735 this->moveTo(x, y);
736 }
737}
738
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739void SkPath::lineTo(SkScalar x, SkScalar y) {
740 SkDEBUGCODE(this->validate();)
741
reed@google.comd335d1d2012-01-12 18:17:11 +0000742 this->injectMoveToIfNeeded();
743
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000744 SkPathRef::Editor ed(&fPathRef);
745 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000746 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000748 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000749 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750}
751
752void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000753 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754 SkPoint pt;
755 this->getLastPt(&pt);
756 this->lineTo(pt.fX + x, pt.fY + y);
757}
758
759void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
760 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000761
reed@google.comd335d1d2012-01-12 18:17:11 +0000762 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000763
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000764 SkPathRef::Editor ed(&fPathRef);
765 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 pts[0].set(x1, y1);
767 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000768 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000769
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000770 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000771 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772}
773
774void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000775 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 SkPoint pt;
777 this->getLastPt(&pt);
778 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
779}
780
reed@google.com277c3f82013-05-31 15:17:50 +0000781void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
782 SkScalar w) {
783 // check for <= 0 or NaN with this test
784 if (!(w > 0)) {
785 this->lineTo(x2, y2);
786 } else if (!SkScalarIsFinite(w)) {
787 this->lineTo(x1, y1);
788 this->lineTo(x2, y2);
789 } else if (SK_Scalar1 == w) {
790 this->quadTo(x1, y1, x2, y2);
791 } else {
792 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000793
reed@google.com277c3f82013-05-31 15:17:50 +0000794 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000795
reed@google.com277c3f82013-05-31 15:17:50 +0000796 SkPathRef::Editor ed(&fPathRef);
797 SkPoint* pts = ed.growForConic(w);
798 pts[0].set(x1, y1);
799 pts[1].set(x2, y2);
800 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000801
reed@google.com277c3f82013-05-31 15:17:50 +0000802 GEN_ID_INC;
803 DIRTY_AFTER_EDIT;
804 }
805}
806
807void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
808 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
813}
814
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
816 SkScalar x3, SkScalar y3) {
817 SkDEBUGCODE(this->validate();)
818
reed@google.comd335d1d2012-01-12 18:17:11 +0000819 this->injectMoveToIfNeeded();
820
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 SkPathRef::Editor ed(&fPathRef);
822 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823 pts[0].set(x1, y1);
824 pts[1].set(x2, y2);
825 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000826 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000828 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000829 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830}
831
832void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
833 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000834 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 SkPoint pt;
836 this->getLastPt(&pt);
837 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
838 pt.fX + x3, pt.fY + y3);
839}
840
841void SkPath::close() {
842 SkDEBUGCODE(this->validate();)
843
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000844 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847 case kLine_Verb:
848 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000849 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 case kMove_Verb: {
852 SkPathRef::Editor ed(&fPathRef);
853 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000854 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 }
reed@google.com277c3f82013-05-31 15:17:50 +0000857 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000858 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000859 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000860 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000861 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000862 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 }
864 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000865
866 // signal that we need a moveTo to follow us (unless we're done)
867#if 0
868 if (fLastMoveToIndex >= 0) {
869 fLastMoveToIndex = ~fLastMoveToIndex;
870 }
871#else
872 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
873#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874}
875
876///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000877
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000878static void assert_known_direction(int dir) {
879 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
880}
881
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882void SkPath::addRect(const SkRect& rect, Direction dir) {
883 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
884}
885
886void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
887 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000888 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000889 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
890 SkAutoDisableDirectionCheck addc(this);
891
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
893
894 this->incReserve(5);
895
896 this->moveTo(left, top);
897 if (dir == kCCW_Direction) {
898 this->lineTo(left, bottom);
899 this->lineTo(right, bottom);
900 this->lineTo(right, top);
901 } else {
902 this->lineTo(right, top);
903 this->lineTo(right, bottom);
904 this->lineTo(left, bottom);
905 }
906 this->close();
907}
908
reed@google.com744faba2012-05-29 19:54:52 +0000909void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
910 SkDEBUGCODE(this->validate();)
911 if (count <= 0) {
912 return;
913 }
914
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000915 SkPathRef::Editor ed(&fPathRef);
916 fLastMoveToIndex = ed.pathRef()->countPoints();
917 uint8_t* vb;
918 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000919 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000920 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000922 memcpy(p, pts, count * sizeof(SkPoint));
923 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000924 if (count > 1) {
925 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
926 // be 0, the compiler will remove the test/branch entirely.
927 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000928 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000929 } else {
930 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000931 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000932 }
933 }
934 fSegmentMask |= kLine_SegmentMask;
935 }
936 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000937 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000938 }
939
940 GEN_ID_INC;
941 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000942 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000943}
944
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945static void add_corner_arc(SkPath* path, const SkRect& rect,
946 SkScalar rx, SkScalar ry, int startAngle,
947 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000948 // These two asserts are not sufficient, since really we want to know
949 // that the pair of radii (e.g. left and right, or top and bottom) sum
950 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000951 // these conservative asserts.
952 SkASSERT(0 <= rx && rx <= rect.width());
953 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000954
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 SkRect r;
956 r.set(-rx, -ry, rx, ry);
957
958 switch (startAngle) {
959 case 0:
960 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
961 break;
962 case 90:
963 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
964 break;
965 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
966 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000967 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968 }
reed@google.comabf15c12011-01-18 20:35:51 +0000969
reed@android.com8a1c16f2008-12-17 15:59:43 +0000970 SkScalar start = SkIntToScalar(startAngle);
971 SkScalar sweep = SkIntToScalar(90);
972 if (SkPath::kCCW_Direction == dir) {
973 start += sweep;
974 sweep = -sweep;
975 }
reed@google.comabf15c12011-01-18 20:35:51 +0000976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977 path->arcTo(r, start, sweep, forceMoveTo);
978}
979
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000980void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000982 SkRRect rrect;
983 rrect.setRectRadii(rect, (const SkVector*) radii);
984 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985}
986
reed@google.com4ed0fb72012-12-12 20:48:18 +0000987void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000988 assert_known_direction(dir);
989
990 if (rrect.isEmpty()) {
991 return;
992 }
993
reed@google.com4ed0fb72012-12-12 20:48:18 +0000994 const SkRect& bounds = rrect.getBounds();
995
996 if (rrect.isRect()) {
997 this->addRect(bounds, dir);
998 } else if (rrect.isOval()) {
999 this->addOval(bounds, dir);
1000 } else if (rrect.isSimple()) {
1001 const SkVector& rad = rrect.getSimpleRadii();
1002 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1003 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001004 SkAutoPathBoundsUpdate apbu(this, bounds);
1005
1006 if (kCW_Direction == dir) {
1007 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1008 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1009 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1010 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1011 } else {
1012 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1013 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1014 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1015 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1016 }
1017 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001018 }
1019}
1020
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001021bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001022 int count = fPathRef->countVerbs();
1023 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1024 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001025 if (*verbs == kLine_Verb ||
1026 *verbs == kQuad_Verb ||
1027 *verbs == kCubic_Verb) {
1028 return false;
1029 }
1030 ++verbs;
1031 }
1032 return true;
1033}
1034
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001035#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1036
1037void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1038 Direction dir) {
1039 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001040
humper@google.com75e3ca12013-04-08 21:44:11 +00001041 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001042 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001043 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001044 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001045 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1046 return;
1047 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001048
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001049 SkScalar w = rect.width();
1050 SkScalar halfW = SkScalarHalf(w);
1051 SkScalar h = rect.height();
1052 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001053
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001054 if (halfW <= 0 || halfH <= 0) {
1055 return;
1056 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001057
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001058 bool skip_hori = rx >= halfW;
1059 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001060
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001061 if (skip_hori && skip_vert) {
1062 this->addOval(rect, dir);
1063 return;
1064 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001065
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001066 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001067
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001068 SkAutoPathBoundsUpdate apbu(this, rect);
1069 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001070
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001071 if (skip_hori) {
1072 rx = halfW;
1073 } else if (skip_vert) {
1074 ry = halfH;
1075 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001076
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001077 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1078 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001079
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001080 this->incReserve(17);
1081 this->moveTo(rect.fRight - rx, rect.fTop);
1082 if (dir == kCCW_Direction) {
1083 if (!skip_hori) {
1084 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1085 }
1086 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1087 rect.fLeft, rect.fTop + ry - sy,
1088 rect.fLeft, rect.fTop + ry); // top-left
1089 if (!skip_vert) {
1090 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1091 }
1092 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1093 rect.fLeft + rx - sx, rect.fBottom,
1094 rect.fLeft + rx, rect.fBottom); // bot-left
1095 if (!skip_hori) {
1096 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1097 }
1098 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1099 rect.fRight, rect.fBottom - ry + sy,
1100 rect.fRight, rect.fBottom - ry); // bot-right
1101 if (!skip_vert) {
1102 this->lineTo(rect.fRight, rect.fTop + ry);
1103 }
1104 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1105 rect.fRight - rx + sx, rect.fTop,
1106 rect.fRight - rx, rect.fTop); // top-right
1107 } else {
1108 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1109 rect.fRight, rect.fTop + ry - sy,
1110 rect.fRight, rect.fTop + ry); // top-right
1111 if (!skip_vert) {
1112 this->lineTo(rect.fRight, rect.fBottom - ry);
1113 }
1114 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1115 rect.fRight - rx + sx, rect.fBottom,
1116 rect.fRight - rx, rect.fBottom); // bot-right
1117 if (!skip_hori) {
1118 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1119 }
1120 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1121 rect.fLeft, rect.fBottom - ry + sy,
1122 rect.fLeft, rect.fBottom - ry); // bot-left
1123 if (!skip_vert) {
1124 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1125 }
1126 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1127 rect.fLeft + rx - sx, rect.fTop,
1128 rect.fLeft + rx, rect.fTop); // top-left
1129 if (!skip_hori) {
1130 this->lineTo(rect.fRight - rx, rect.fTop); // top
1131 }
1132 }
1133 this->close();
1134}
1135
reed@android.com8a1c16f2008-12-17 15:59:43 +00001136void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001137 assert_known_direction(dir);
1138
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001139 /* If addOval() is called after previous moveTo(),
1140 this path is still marked as an oval. This is used to
1141 fit into WebKit's calling sequences.
1142 We can't simply check isEmpty() in this case, as additional
1143 moveTo() would mark the path non empty.
1144 */
1145 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001146 if (fIsOval) {
1147 fDirection = dir;
1148 } else {
1149 fDirection = kUnknown_Direction;
1150 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001151
1152 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001153 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001154
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 SkAutoPathBoundsUpdate apbu(this, oval);
1156
1157 SkScalar cx = oval.centerX();
1158 SkScalar cy = oval.centerY();
1159 SkScalar rx = SkScalarHalf(oval.width());
1160 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1163 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1164 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1165 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1166
1167 /*
1168 To handle imprecision in computing the center and radii, we revert to
1169 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1170 to ensure that we don't exceed the oval's bounds *ever*, since we want
1171 to use oval for our fast-bounds, rather than have to recompute it.
1172 */
1173 const SkScalar L = oval.fLeft; // cx - rx
1174 const SkScalar T = oval.fTop; // cy - ry
1175 const SkScalar R = oval.fRight; // cx + rx
1176 const SkScalar B = oval.fBottom; // cy + ry
1177
1178 this->incReserve(17); // 8 quads + close
1179 this->moveTo(R, cy);
1180 if (dir == kCCW_Direction) {
1181 this->quadTo( R, cy - sy, cx + mx, cy - my);
1182 this->quadTo(cx + sx, T, cx , T);
1183 this->quadTo(cx - sx, T, cx - mx, cy - my);
1184 this->quadTo( L, cy - sy, L, cy );
1185 this->quadTo( L, cy + sy, cx - mx, cy + my);
1186 this->quadTo(cx - sx, B, cx , B);
1187 this->quadTo(cx + sx, B, cx + mx, cy + my);
1188 this->quadTo( R, cy + sy, R, cy );
1189 } else {
1190 this->quadTo( R, cy + sy, cx + mx, cy + my);
1191 this->quadTo(cx + sx, B, cx , B);
1192 this->quadTo(cx - sx, B, cx - mx, cy + my);
1193 this->quadTo( L, cy + sy, L, cy );
1194 this->quadTo( L, cy - sy, cx - mx, cy - my);
1195 this->quadTo(cx - sx, T, cx , T);
1196 this->quadTo(cx + sx, T, cx + mx, cy - my);
1197 this->quadTo( R, cy - sy, R, cy );
1198 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 this->close();
1200}
1201
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001202bool SkPath::isOval(SkRect* rect) const {
1203 if (fIsOval && rect) {
1204 *rect = getBounds();
1205 }
1206
1207 return fIsOval;
1208}
1209
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1211 if (r > 0) {
1212 SkRect rect;
1213 rect.set(x - r, y - r, x + r, y + r);
1214 this->addOval(rect, dir);
1215 }
1216}
1217
1218#include "SkGeometry.h"
1219
1220static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1221 SkScalar sweepAngle,
1222 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001223
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001224 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001225 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1226 // Chrome uses this path to move into and out of ovals. If not
1227 // treated as a special case the moves can distort the oval's
1228 // bounding box (and break the circle special case).
1229 pts[0].set(oval.fRight, oval.centerY());
1230 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001231 } else if (0 == oval.width() && 0 == oval.height()) {
1232 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001233 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001234 // a rect.
1235 // TODO: optimizing the case where only one of width or height is zero
1236 // should also be considered. This case, however, doesn't seem to be
1237 // as common as the single point case.
1238 pts[0].set(oval.fRight, oval.fTop);
1239 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001240 }
1241
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 SkVector start, stop;
1243
1244 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1245 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1246 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001247
1248 /* If the sweep angle is nearly (but less than) 360, then due to precision
1249 loss in radians-conversion and/or sin/cos, we may end up with coincident
1250 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1251 of drawing a nearly complete circle (good).
1252 e.g. canvas.drawArc(0, 359.99, ...)
1253 -vs- canvas.drawArc(0, 359.9, ...)
1254 We try to detect this edge case, and tweak the stop vector
1255 */
1256 if (start == stop) {
1257 SkScalar sw = SkScalarAbs(sweepAngle);
1258 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1259 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1260 // make a guess at a tiny angle (in radians) to tweak by
1261 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1262 // not sure how much will be enough, so we use a loop
1263 do {
1264 stopRad -= deltaRad;
1265 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1266 } while (start == stop);
1267 }
1268 }
1269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1273 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001274
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 return SkBuildQuadArc(start, stop,
1276 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1277 &matrix, pts);
1278}
1279
1280void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1281 bool forceMoveTo) {
1282 if (oval.width() < 0 || oval.height() < 0) {
1283 return;
1284 }
1285
1286 SkPoint pts[kSkBuildQuadArcStorage];
1287 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1288 SkASSERT((count & 1) == 1);
1289
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001290 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 forceMoveTo = true;
1292 }
1293 this->incReserve(count);
1294 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1295 for (int i = 1; i < count; i += 2) {
1296 this->quadTo(pts[i], pts[i+1]);
1297 }
1298}
1299
1300void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1301 SkScalar sweepAngle) {
1302 if (oval.isEmpty() || 0 == sweepAngle) {
1303 return;
1304 }
1305
1306 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1307
1308 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1309 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1310 return;
1311 }
1312
1313 SkPoint pts[kSkBuildQuadArcStorage];
1314 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1315
1316 this->incReserve(count);
1317 this->moveTo(pts[0]);
1318 for (int i = 1; i < count; i += 2) {
1319 this->quadTo(pts[i], pts[i+1]);
1320 }
1321}
1322
1323/*
1324 Need to handle the case when the angle is sharp, and our computed end-points
1325 for the arc go behind pt1 and/or p2...
1326*/
1327void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1328 SkScalar radius) {
1329 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001330
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 // need to know our prev pt so we can construct tangent vectors
1332 {
1333 SkPoint start;
1334 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001335 // Handle degenerate cases by adding a line to the first point and
1336 // bailing out.
1337 if ((x1 == start.fX && y1 == start.fY) ||
1338 (x1 == x2 && y1 == y2) ||
1339 radius == 0) {
1340 this->lineTo(x1, y1);
1341 return;
1342 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343 before.setNormalize(x1 - start.fX, y1 - start.fY);
1344 after.setNormalize(x2 - x1, y2 - y1);
1345 }
reed@google.comabf15c12011-01-18 20:35:51 +00001346
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 SkScalar cosh = SkPoint::DotProduct(before, after);
1348 SkScalar sinh = SkPoint::CrossProduct(before, after);
1349
1350 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001351 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352 return;
1353 }
reed@google.comabf15c12011-01-18 20:35:51 +00001354
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1356 if (dist < 0) {
1357 dist = -dist;
1358 }
1359
1360 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1361 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1362 SkRotationDirection arcDir;
1363
1364 // now turn before/after into normals
1365 if (sinh > 0) {
1366 before.rotateCCW();
1367 after.rotateCCW();
1368 arcDir = kCW_SkRotationDirection;
1369 } else {
1370 before.rotateCW();
1371 after.rotateCW();
1372 arcDir = kCCW_SkRotationDirection;
1373 }
1374
1375 SkMatrix matrix;
1376 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001377
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 matrix.setScale(radius, radius);
1379 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1380 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001381
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001383
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 this->incReserve(count);
1385 // [xx,yy] == pts[0]
1386 this->lineTo(xx, yy);
1387 for (int i = 1; i < count; i += 2) {
1388 this->quadTo(pts[i], pts[i+1]);
1389 }
1390}
1391
1392///////////////////////////////////////////////////////////////////////////////
1393
1394void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1395 SkMatrix matrix;
1396
1397 matrix.setTranslate(dx, dy);
1398 this->addPath(path, matrix);
1399}
1400
1401void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001402 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001404 fIsOval = false;
1405
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001406 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkPoint pts[4];
1408 Verb verb;
1409
1410 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1411
1412 while ((verb = iter.next(pts)) != kDone_Verb) {
1413 switch (verb) {
1414 case kMove_Verb:
1415 proc(matrix, &pts[0], &pts[0], 1);
1416 this->moveTo(pts[0]);
1417 break;
1418 case kLine_Verb:
1419 proc(matrix, &pts[1], &pts[1], 1);
1420 this->lineTo(pts[1]);
1421 break;
1422 case kQuad_Verb:
1423 proc(matrix, &pts[1], &pts[1], 2);
1424 this->quadTo(pts[1], pts[2]);
1425 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001426 case kConic_Verb:
1427 proc(matrix, &pts[1], &pts[1], 2);
1428 this->conicTo(pts[1], pts[2], iter.conicWeight());
1429 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 case kCubic_Verb:
1431 proc(matrix, &pts[1], &pts[1], 3);
1432 this->cubicTo(pts[1], pts[2], pts[3]);
1433 break;
1434 case kClose_Verb:
1435 this->close();
1436 break;
1437 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001438 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 }
1440 }
1441}
1442
1443///////////////////////////////////////////////////////////////////////////////
1444
reed@google.com277c3f82013-05-31 15:17:50 +00001445static int pts_in_verb(unsigned verb) {
1446 static const uint8_t gPtsInVerb[] = {
1447 1, // kMove
1448 1, // kLine
1449 2, // kQuad
1450 2, // kConic
1451 3, // kCubic
1452 0, // kClose
1453 0 // kDone
1454 };
1455
1456 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1457 return gPtsInVerb[verb];
1458}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459
1460// ignore the initial moveto, and stop when the 1st contour ends
1461void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 int i, vcount = path.fPathRef->countVerbs();
1463 // exit early if the path is empty, or just has a moveTo.
1464 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 return;
1466 }
1467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001470 fIsOval = false;
1471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 const uint8_t* verbs = path.fPathRef->verbs();
1473 // skip the initial moveTo
1474 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001475 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001477 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001479 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 case kLine_Verb:
1481 this->lineTo(pts[0].fX, pts[0].fY);
1482 break;
1483 case kQuad_Verb:
1484 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1485 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001486 case kConic_Verb:
1487 this->conicTo(pts[0], pts[1], *conicWeight++);
1488 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001490 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 +00001491 break;
1492 case kClose_Verb:
1493 return;
1494 }
reed@google.com277c3f82013-05-31 15:17:50 +00001495 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 }
1497}
1498
1499// ignore the last point of the 1st contour
1500void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001501 int i, vcount = path.fPathRef->countVerbs();
1502 // exit early if the path is empty, or just has a moveTo.
1503 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 return;
1505 }
1506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001509 fIsOval = false;
1510
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001511 const uint8_t* verbs = path.fPathRef->verbs();
1512 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001513 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001515 SkASSERT(verbs[~0] == kMove_Verb);
1516 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001517 unsigned v = verbs[~i];
1518 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 if (n == 0) {
1520 break;
1521 }
1522 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001523 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 }
1525
1526 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 case kLine_Verb:
1529 this->lineTo(pts[-1].fX, pts[-1].fY);
1530 break;
1531 case kQuad_Verb:
1532 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1533 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001534 case kConic_Verb:
1535 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1536 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 case kCubic_Verb:
1538 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1539 pts[-3].fX, pts[-3].fY);
1540 break;
1541 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001542 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 break;
1544 }
reed@google.com277c3f82013-05-31 15:17:50 +00001545 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 }
1547}
1548
reed@google.com63d73742012-01-10 15:33:12 +00001549void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001551
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 const SkPoint* pts = src.fPathRef->pointsEnd();
1553 // we will iterator through src's verbs backwards
1554 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1555 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001556 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001557
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001558 fIsOval = false;
1559
reed@google.com63d73742012-01-10 15:33:12 +00001560 bool needMove = true;
1561 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001562 while (verbs < verbsEnd) {
1563 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001564 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001565
1566 if (needMove) {
1567 --pts;
1568 this->moveTo(pts->fX, pts->fY);
1569 needMove = false;
1570 }
1571 pts -= n;
1572 switch (v) {
1573 case kMove_Verb:
1574 if (needClose) {
1575 this->close();
1576 needClose = false;
1577 }
1578 needMove = true;
1579 pts += 1; // so we see the point in "if (needMove)" above
1580 break;
1581 case kLine_Verb:
1582 this->lineTo(pts[0]);
1583 break;
1584 case kQuad_Verb:
1585 this->quadTo(pts[1], pts[0]);
1586 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001587 case kConic_Verb:
1588 this->conicTo(pts[1], pts[0], *--conicWeights);
1589 break;
reed@google.com63d73742012-01-10 15:33:12 +00001590 case kCubic_Verb:
1591 this->cubicTo(pts[2], pts[1], pts[0]);
1592 break;
1593 case kClose_Verb:
1594 needClose = true;
1595 break;
1596 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001597 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001598 }
1599 }
1600}
1601
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602///////////////////////////////////////////////////////////////////////////////
1603
1604void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1605 SkMatrix matrix;
1606
1607 matrix.setTranslate(dx, dy);
1608 this->transform(matrix, dst);
1609}
1610
1611#include "SkGeometry.h"
1612
1613static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1614 int level = 2) {
1615 if (--level >= 0) {
1616 SkPoint tmp[5];
1617
1618 SkChopQuadAtHalf(pts, tmp);
1619 subdivide_quad_to(path, &tmp[0], level);
1620 subdivide_quad_to(path, &tmp[2], level);
1621 } else {
1622 path->quadTo(pts[1], pts[2]);
1623 }
1624}
1625
1626static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1627 int level = 2) {
1628 if (--level >= 0) {
1629 SkPoint tmp[7];
1630
1631 SkChopCubicAtHalf(pts, tmp);
1632 subdivide_cubic_to(path, &tmp[0], level);
1633 subdivide_cubic_to(path, &tmp[3], level);
1634 } else {
1635 path->cubicTo(pts[1], pts[2], pts[3]);
1636 }
1637}
1638
1639void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1640 SkDEBUGCODE(this->validate();)
1641 if (dst == NULL) {
1642 dst = (SkPath*)this;
1643 }
1644
tomhudson@google.com8d430182011-06-06 19:11:19 +00001645 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 SkPath tmp;
1647 tmp.fFillType = fFillType;
1648
1649 SkPath::Iter iter(*this, false);
1650 SkPoint pts[4];
1651 SkPath::Verb verb;
1652
reed@google.com4a3b7142012-05-16 17:16:46 +00001653 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 switch (verb) {
1655 case kMove_Verb:
1656 tmp.moveTo(pts[0]);
1657 break;
1658 case kLine_Verb:
1659 tmp.lineTo(pts[1]);
1660 break;
1661 case kQuad_Verb:
1662 subdivide_quad_to(&tmp, pts);
1663 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001664 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001665 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001666 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1667 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 case kCubic_Verb:
1669 subdivide_cubic_to(&tmp, pts);
1670 break;
1671 case kClose_Verb:
1672 tmp.close();
1673 break;
1674 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001675 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 break;
1677 }
1678 }
1679
1680 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001681 SkPathRef::Editor ed(&dst->fPathRef);
1682 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001683 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001685 /*
1686 * If we're not in perspective, we can transform all of the points at
1687 * once.
1688 *
1689 * Here we also want to optimize bounds, by noting if the bounds are
1690 * already known, and if so, we just transform those as well and mark
1691 * them as "known", rather than force the transformed path to have to
1692 * recompute them.
1693 *
1694 * Special gotchas if the path is effectively empty (<= 1 point) or
1695 * if it is non-finite. In those cases bounds need to stay empty,
1696 * regardless of the matrix.
1697 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001698 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001699 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001700 if (fIsFinite) {
1701 matrix.mapRect(&dst->fBounds, fBounds);
1702 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1703 dst->fBounds.setEmpty();
1704 }
1705 } else {
1706 dst->fIsFinite = false;
1707 dst->fBounds.setEmpty();
1708 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001710 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001711 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 }
1713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001714 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1715
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001718 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001719 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001721
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001722#ifdef SK_BUILD_FOR_ANDROID
1723 if (!matrix.isIdentity()) {
1724 GEN_ID_PTR_INC(dst);
1725 }
1726#endif
1727
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001728 if (kUnknown_Direction == fDirection) {
1729 dst->fDirection = kUnknown_Direction;
1730 } else {
1731 SkScalar det2x2 =
1732 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1733 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1734 if (det2x2 < 0) {
1735 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1736 } else if (det2x2 > 0) {
1737 dst->fDirection = fDirection;
1738 } else {
1739 dst->fDirection = kUnknown_Direction;
1740 }
1741 }
1742
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001743 // It's an oval only if it stays a rect.
1744 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001745
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 SkDEBUGCODE(dst->validate();)
1747 }
1748}
1749
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750///////////////////////////////////////////////////////////////////////////////
1751///////////////////////////////////////////////////////////////////////////////
1752
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001753enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001754 kEmptyContour_SegmentState, // The current contour is empty. We may be
1755 // starting processing or we may have just
1756 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1758 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1759 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760};
1761
1762SkPath::Iter::Iter() {
1763#ifdef SK_DEBUG
1764 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001765 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001768 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769#endif
1770 // need to init enough to make next() harmlessly return kDone_Verb
1771 fVerbs = NULL;
1772 fVerbStop = NULL;
1773 fNeedClose = false;
1774}
1775
1776SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1777 this->setPath(path, forceClose);
1778}
1779
1780void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001781 fPts = path.fPathRef->points();
1782 fVerbs = path.fPathRef->verbs();
1783 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001784 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001785 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001786 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 fForceClose = SkToU8(forceClose);
1788 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001789 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790}
1791
1792bool SkPath::Iter::isClosedContour() const {
1793 if (fVerbs == NULL || fVerbs == fVerbStop) {
1794 return false;
1795 }
1796 if (fForceClose) {
1797 return true;
1798 }
1799
1800 const uint8_t* verbs = fVerbs;
1801 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001802
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001803 if (kMove_Verb == *(verbs - 1)) {
1804 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 }
1806
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 while (verbs > stop) {
1808 // verbs points one beyond the current verb, decrement first.
1809 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 if (kMove_Verb == v) {
1811 break;
1812 }
1813 if (kClose_Verb == v) {
1814 return true;
1815 }
1816 }
1817 return false;
1818}
1819
1820SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001823 // A special case: if both points are NaN, SkPoint::operation== returns
1824 // false, but the iterator expects that they are treated as the same.
1825 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001826 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1827 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001828 return kClose_Verb;
1829 }
1830
reed@google.com9e25dbf2012-05-15 17:05:38 +00001831 pts[0] = fLastPt;
1832 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 fLastPt = fMoveTo;
1834 fCloseLine = true;
1835 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001836 } else {
1837 pts[0] = fMoveTo;
1838 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840}
1841
reed@google.com9e25dbf2012-05-15 17:05:38 +00001842const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 if (fSegmentState == kAfterMove_SegmentState) {
1844 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001846 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1849 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001850 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852}
1853
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854void SkPath::Iter::consumeDegenerateSegments() {
1855 // We need to step over anything that will not move the current draw point
1856 // forward before the next move is seen
1857 const uint8_t* lastMoveVerb = 0;
1858 const SkPoint* lastMovePt = 0;
1859 SkPoint lastPt = fLastPt;
1860 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001861 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 switch (verb) {
1863 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 // Keep a record of this most recent move
1865 lastMoveVerb = fVerbs;
1866 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001867 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001869 fPts++;
1870 break;
1871
1872 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001873 // A close when we are in a segment is always valid except when it
1874 // follows a move which follows a segment.
1875 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 return;
1877 }
1878 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001879 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 break;
1881
1882 case kLine_Verb:
1883 if (!IsLineDegenerate(lastPt, fPts[0])) {
1884 if (lastMoveVerb) {
1885 fVerbs = lastMoveVerb;
1886 fPts = lastMovePt;
1887 return;
1888 }
1889 return;
1890 }
1891 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001892 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 fPts++;
1894 break;
1895
reed@google.com277c3f82013-05-31 15:17:50 +00001896 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 case kQuad_Verb:
1898 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1899 if (lastMoveVerb) {
1900 fVerbs = lastMoveVerb;
1901 fPts = lastMovePt;
1902 return;
1903 }
1904 return;
1905 }
1906 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001907 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001909 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 break;
1911
1912 case kCubic_Verb:
1913 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1914 if (lastMoveVerb) {
1915 fVerbs = lastMoveVerb;
1916 fPts = lastMovePt;
1917 return;
1918 }
1919 return;
1920 }
1921 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 fPts += 3;
1924 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001925
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001927 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 }
1929 }
1930}
1931
reed@google.com4a3b7142012-05-16 17:16:46 +00001932SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001933 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 // Close the curve if requested and if there is some curve to close
1937 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001938 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 return kLine_Verb;
1940 }
1941 fNeedClose = false;
1942 return kClose_Verb;
1943 }
1944 return kDone_Verb;
1945 }
1946
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 // fVerbs is one beyond the current verb, decrement first
1948 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949 const SkPoint* SK_RESTRICT srcPts = fPts;
1950 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951
1952 switch (verb) {
1953 case kMove_Verb:
1954 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 verb = this->autoClose(pts);
1957 if (verb == kClose_Verb) {
1958 fNeedClose = false;
1959 }
1960 return (Verb)verb;
1961 }
1962 if (fVerbs == fVerbStop) { // might be a trailing moveto
1963 return kDone_Verb;
1964 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001965 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001968 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fNeedClose = fForceClose;
1971 break;
1972 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001973 pts[0] = this->cons_moveTo();
1974 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 fLastPt = srcPts[0];
1976 fCloseLine = false;
1977 srcPts += 1;
1978 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001979 case kConic_Verb:
1980 fConicWeights += 1;
1981 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001983 pts[0] = this->cons_moveTo();
1984 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 fLastPt = srcPts[1];
1986 srcPts += 2;
1987 break;
1988 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001989 pts[0] = this->cons_moveTo();
1990 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991 fLastPt = srcPts[2];
1992 srcPts += 3;
1993 break;
1994 case kClose_Verb:
1995 verb = this->autoClose(pts);
1996 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001997 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 } else {
1999 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002000 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 break;
2004 }
2005 fPts = srcPts;
2006 return (Verb)verb;
2007}
2008
2009///////////////////////////////////////////////////////////////////////////////
2010
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002011SkPath::RawIter::RawIter() {
2012#ifdef SK_DEBUG
2013 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002014 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002015 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2016#endif
2017 // need to init enough to make next() harmlessly return kDone_Verb
2018 fVerbs = NULL;
2019 fVerbStop = NULL;
2020}
2021
2022SkPath::RawIter::RawIter(const SkPath& path) {
2023 this->setPath(path);
2024}
2025
2026void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002027 fPts = path.fPathRef->points();
2028 fVerbs = path.fPathRef->verbs();
2029 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002030 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002031 fMoveTo.fX = fMoveTo.fY = 0;
2032 fLastPt.fX = fLastPt.fY = 0;
2033}
2034
2035SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002036 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 if (fVerbs == fVerbStop) {
2038 return kDone_Verb;
2039 }
2040
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002041 // fVerbs points one beyond next verb so decrement first.
2042 unsigned verb = *(--fVerbs);
2043 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002044
2045 switch (verb) {
2046 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002047 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002048 fMoveTo = srcPts[0];
2049 fLastPt = fMoveTo;
2050 srcPts += 1;
2051 break;
2052 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002053 pts[0] = fLastPt;
2054 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002055 fLastPt = srcPts[0];
2056 srcPts += 1;
2057 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002058 case kConic_Verb:
2059 fConicWeights += 1;
2060 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002061 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002062 pts[0] = fLastPt;
2063 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002064 fLastPt = srcPts[1];
2065 srcPts += 2;
2066 break;
2067 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002068 pts[0] = fLastPt;
2069 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002070 fLastPt = srcPts[2];
2071 srcPts += 3;
2072 break;
2073 case kClose_Verb:
2074 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002075 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002076 break;
2077 }
2078 fPts = srcPts;
2079 return (Verb)verb;
2080}
2081
2082///////////////////////////////////////////////////////////////////////////////
2083
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002085 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086*/
2087
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002088uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089 SkDEBUGCODE(this->validate();)
2090
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002091 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002092 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002093 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002094 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002095 return SkAlign4(byteCount);
2096 }
2097
2098 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002099
2100 // Call getBounds() to ensure (as a side-effect) that fBounds
2101 // and fIsFinite are computed.
2102 const SkRect& bounds = this->getBounds();
2103 SkASSERT(!fBoundsIsDirty);
2104
2105 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2106 ((fIsOval & 1) << kIsOval_SerializationShift) |
2107 (fConvexity << kConvexity_SerializationShift) |
2108 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002109 (fSegmentMask << kSegmentMask_SerializationShift) |
2110 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002111
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002112 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002113
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002114 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002115
2116 buffer.write(&bounds, sizeof(bounds));
2117
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002118 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002119 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120}
2121
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002122uint32_t SkPath::readFromMemory(const void* storage) {
2123 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002124
reed@google.com98b11f12011-09-21 18:40:27 +00002125 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002126 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2127 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2128 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2129 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002130 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002131 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002132
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002133 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002134
2135 buffer.read(&fBounds, sizeof(fBounds));
2136 fBoundsIsDirty = false;
2137
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002138 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002139
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002140 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141
2142 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002143 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144}
2145
2146///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147
reed@google.com51bbe752013-01-17 22:07:50 +00002148#include "SkString.h"
2149
2150static void append_scalar(SkString* str, SkScalar value) {
2151 SkString tmp;
2152 tmp.printf("%g", value);
2153 if (tmp.contains('.')) {
2154 tmp.appendUnichar('f');
2155 }
2156 str->append(tmp);
2157}
2158
2159static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002160 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002161 str->append(label);
2162 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002163
reed@google.com51bbe752013-01-17 22:07:50 +00002164 const SkScalar* values = &pts[0].fX;
2165 count *= 2;
2166
2167 for (int i = 0; i < count; ++i) {
2168 append_scalar(str, values[i]);
2169 if (i < count - 1) {
2170 str->append(", ");
2171 }
2172 }
reed@google.com277c3f82013-05-31 15:17:50 +00002173 if (conicWeight >= 0) {
2174 str->append(", ");
2175 append_scalar(str, conicWeight);
2176 }
reed@google.com51bbe752013-01-17 22:07:50 +00002177 str->append(");\n");
2178}
2179
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180void SkPath::dump(bool forceClose, const char title[]) const {
2181 Iter iter(*this, forceClose);
2182 SkPoint pts[4];
2183 Verb verb;
2184
2185 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2186 title ? title : "");
2187
reed@google.com51bbe752013-01-17 22:07:50 +00002188 SkString builder;
2189
reed@google.com4a3b7142012-05-16 17:16:46 +00002190 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002191 switch (verb) {
2192 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002193 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 break;
2195 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002196 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 break;
2198 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002199 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002201 case kConic_Verb:
2202 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2203 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002204 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002205 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002206 break;
2207 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002208 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002209 break;
2210 default:
2211 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2212 verb = kDone_Verb; // stop the loop
2213 break;
2214 }
2215 }
reed@google.com51bbe752013-01-17 22:07:50 +00002216 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002217}
2218
reed@android.come522ca52009-11-23 20:10:41 +00002219void SkPath::dump() const {
2220 this->dump(false);
2221}
2222
2223#ifdef SK_DEBUG
2224void SkPath::validate() const {
2225 SkASSERT(this != NULL);
2226 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002227
djsollen@google.com077348c2012-10-22 20:23:32 +00002228#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002229 if (!fBoundsIsDirty) {
2230 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002231
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002232 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002233 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002234
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002235 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002236 // if we're empty, fBounds may be empty but translated, so we can't
2237 // necessarily compare to bounds directly
2238 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2239 // be [2, 2, 2, 2]
2240 SkASSERT(bounds.isEmpty());
2241 SkASSERT(fBounds.isEmpty());
2242 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002243 if (bounds.isEmpty()) {
2244 SkASSERT(fBounds.isEmpty());
2245 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002246 if (!fBounds.isEmpty()) {
2247 SkASSERT(fBounds.contains(bounds));
2248 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002249 }
reed@android.come522ca52009-11-23 20:10:41 +00002250 }
2251 }
reed@google.com10296cc2011-09-21 12:29:05 +00002252
2253 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002254 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2255 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2256 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002257 case kLine_Verb:
2258 mask |= kLine_SegmentMask;
2259 break;
2260 case kQuad_Verb:
2261 mask |= kQuad_SegmentMask;
2262 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002263 case kConic_Verb:
2264 mask |= kConic_SegmentMask;
2265 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002266 case kCubic_Verb:
2267 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002268 case kMove_Verb: // these verbs aren't included in the segment mask.
2269 case kClose_Verb:
2270 break;
2271 case kDone_Verb:
2272 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2273 break;
2274 default:
2275 SkDEBUGFAIL("Unknown Verb");
2276 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002277 }
2278 }
2279 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002280#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002281}
djsollen@google.com077348c2012-10-22 20:23:32 +00002282#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002283
reed@google.com04863fa2011-05-15 04:08:24 +00002284///////////////////////////////////////////////////////////////////////////////
2285
reed@google.com0b7b9822011-05-16 12:29:27 +00002286static int sign(SkScalar x) { return x < 0; }
2287#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002288
reed@google.com04863fa2011-05-15 04:08:24 +00002289static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002290 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002291}
2292
2293// only valid for a single contour
2294struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002295 Convexicator()
2296 : fPtCount(0)
2297 , fConvexity(SkPath::kConvex_Convexity)
2298 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002299 fSign = 0;
2300 // warnings
2301 fCurrPt.set(0, 0);
2302 fVec0.set(0, 0);
2303 fVec1.set(0, 0);
2304 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002305
2306 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002307 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002308 }
2309
2310 SkPath::Convexity getConvexity() const { return fConvexity; }
2311
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002312 /** The direction returned is only valid if the path is determined convex */
2313 SkPath::Direction getDirection() const { return fDirection; }
2314
reed@google.com04863fa2011-05-15 04:08:24 +00002315 void addPt(const SkPoint& pt) {
2316 if (SkPath::kConcave_Convexity == fConvexity) {
2317 return;
2318 }
2319
2320 if (0 == fPtCount) {
2321 fCurrPt = pt;
2322 ++fPtCount;
2323 } else {
2324 SkVector vec = pt - fCurrPt;
2325 if (vec.fX || vec.fY) {
2326 fCurrPt = pt;
2327 if (++fPtCount == 2) {
2328 fFirstVec = fVec1 = vec;
2329 } else {
2330 SkASSERT(fPtCount > 2);
2331 this->addVec(vec);
2332 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002333
reed@google.com85b6e392011-05-15 20:25:17 +00002334 int sx = sign(vec.fX);
2335 int sy = sign(vec.fY);
2336 fDx += (sx != fSx);
2337 fDy += (sy != fSy);
2338 fSx = sx;
2339 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002340
reed@google.com85b6e392011-05-15 20:25:17 +00002341 if (fDx > 3 || fDy > 3) {
2342 fConvexity = SkPath::kConcave_Convexity;
2343 }
reed@google.com04863fa2011-05-15 04:08:24 +00002344 }
2345 }
2346 }
2347
2348 void close() {
2349 if (fPtCount > 2) {
2350 this->addVec(fFirstVec);
2351 }
2352 }
2353
2354private:
2355 void addVec(const SkVector& vec) {
2356 SkASSERT(vec.fX || vec.fY);
2357 fVec0 = fVec1;
2358 fVec1 = vec;
2359 int sign = CrossProductSign(fVec0, fVec1);
2360 if (0 == fSign) {
2361 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002362 if (1 == sign) {
2363 fDirection = SkPath::kCW_Direction;
2364 } else if (-1 == sign) {
2365 fDirection = SkPath::kCCW_Direction;
2366 }
reed@google.com04863fa2011-05-15 04:08:24 +00002367 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002368 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002369 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002370 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002371 }
2372 }
2373 }
2374
2375 SkPoint fCurrPt;
2376 SkVector fVec0, fVec1, fFirstVec;
2377 int fPtCount; // non-degenerate points
2378 int fSign;
2379 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002380 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002381 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002382};
2383
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002384SkPath::Convexity SkPath::internalGetConvexity() const {
2385 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002386 SkPoint pts[4];
2387 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002389
2390 int contourCount = 0;
2391 int count;
2392 Convexicator state;
2393
2394 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2395 switch (verb) {
2396 case kMove_Verb:
2397 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002398 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002399 return kConcave_Convexity;
2400 }
2401 pts[1] = pts[0];
2402 count = 1;
2403 break;
2404 case kLine_Verb: count = 1; break;
2405 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002406 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 case kCubic_Verb: count = 3; break;
2408 case kClose_Verb:
2409 state.close();
2410 count = 0;
2411 break;
2412 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002413 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002414 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002415 return kConcave_Convexity;
2416 }
2417
2418 for (int i = 1; i <= count; i++) {
2419 state.addPt(pts[i]);
2420 }
2421 // early exit
2422 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002423 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002424 return kConcave_Convexity;
2425 }
2426 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002427 fConvexity = state.getConvexity();
2428 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2429 fDirection = state.getDirection();
2430 }
2431 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002432}
reed@google.com69a99432012-01-10 18:00:10 +00002433
2434///////////////////////////////////////////////////////////////////////////////
2435
2436class ContourIter {
2437public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002438 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002439
2440 bool done() const { return fDone; }
2441 // if !done() then these may be called
2442 int count() const { return fCurrPtCount; }
2443 const SkPoint* pts() const { return fCurrPt; }
2444 void next();
2445
2446private:
2447 int fCurrPtCount;
2448 const SkPoint* fCurrPt;
2449 const uint8_t* fCurrVerb;
2450 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002451 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002452 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002453 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002454};
2455
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002456ContourIter::ContourIter(const SkPathRef& pathRef) {
2457 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002458 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002459 fCurrPt = pathRef.points();
2460 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002461 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002462 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002463 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002464 this->next();
2465}
2466
2467void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002468 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002469 fDone = true;
2470 }
2471 if (fDone) {
2472 return;
2473 }
2474
2475 // skip pts of prev contour
2476 fCurrPt += fCurrPtCount;
2477
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002478 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002479 int ptCount = 1; // moveTo
2480 const uint8_t* verbs = fCurrVerb;
2481
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002482 for (--verbs; verbs > fStopVerbs; --verbs) {
2483 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002484 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002485 goto CONTOUR_END;
2486 case SkPath::kLine_Verb:
2487 ptCount += 1;
2488 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002489 case SkPath::kConic_Verb:
2490 fCurrConicWeight += 1;
2491 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002492 case SkPath::kQuad_Verb:
2493 ptCount += 2;
2494 break;
2495 case SkPath::kCubic_Verb:
2496 ptCount += 3;
2497 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002498 case SkPath::kClose_Verb:
2499 break;
2500 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002501 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002502 break;
2503 }
2504 }
2505CONTOUR_END:
2506 fCurrPtCount = ptCount;
2507 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002508 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002509}
2510
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002511// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002512static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002513 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2514 // We may get 0 when the above subtracts underflow. We expect this to be
2515 // very rare and lazily promote to double.
2516 if (0 == cross) {
2517 double p0x = SkScalarToDouble(p0.fX);
2518 double p0y = SkScalarToDouble(p0.fY);
2519
2520 double p1x = SkScalarToDouble(p1.fX);
2521 double p1y = SkScalarToDouble(p1.fY);
2522
2523 double p2x = SkScalarToDouble(p2.fX);
2524 double p2y = SkScalarToDouble(p2.fY);
2525
2526 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2527 (p1y - p0y) * (p2x - p0x));
2528
2529 }
2530 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002531}
2532
reed@google.comc1ea60a2012-01-31 15:15:36 +00002533// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002534static int find_max_y(const SkPoint pts[], int count) {
2535 SkASSERT(count > 0);
2536 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002537 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002538 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002539 SkScalar y = pts[i].fY;
2540 if (y > max) {
2541 max = y;
2542 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002543 }
2544 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002545 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002546}
2547
reed@google.comcabaf1d2012-01-11 21:03:05 +00002548static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2549 int i = index;
2550 for (;;) {
2551 i = (i + inc) % n;
2552 if (i == index) { // we wrapped around, so abort
2553 break;
2554 }
2555 if (pts[index] != pts[i]) { // found a different point, success!
2556 break;
2557 }
2558 }
2559 return i;
2560}
2561
reed@google.comc1ea60a2012-01-31 15:15:36 +00002562/**
2563 * Starting at index, and moving forward (incrementing), find the xmin and
2564 * xmax of the contiguous points that have the same Y.
2565 */
2566static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2567 int* maxIndexPtr) {
2568 const SkScalar y = pts[index].fY;
2569 SkScalar min = pts[index].fX;
2570 SkScalar max = min;
2571 int minIndex = index;
2572 int maxIndex = index;
2573 for (int i = index + 1; i < n; ++i) {
2574 if (pts[i].fY != y) {
2575 break;
2576 }
2577 SkScalar x = pts[i].fX;
2578 if (x < min) {
2579 min = x;
2580 minIndex = i;
2581 } else if (x > max) {
2582 max = x;
2583 maxIndex = i;
2584 }
2585 }
2586 *maxIndexPtr = maxIndex;
2587 return minIndex;
2588}
2589
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002590static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002591 if (dir) {
2592 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2593 }
reed@google.comac8543f2012-01-30 20:51:25 +00002594}
2595
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596#if 0
2597#include "SkString.h"
2598#include "../utils/SkParsePath.h"
2599static void dumpPath(const SkPath& path) {
2600 SkString str;
2601 SkParsePath::ToSVGString(path, &str);
2602 SkDebugf("%s\n", str.c_str());
2603}
2604#endif
2605
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002606namespace {
2607// for use with convex_dir_test
2608double mul(double a, double b) { return a * b; }
2609SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2610double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2611SkScalar toScalar(SkScalar a) { return a; }
2612
2613// determines the winding direction of a convex polygon with the precision
2614// of T. CAST_SCALAR casts an SkScalar to T.
2615template <typename T, T (CAST_SCALAR)(SkScalar)>
2616bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2617 // we find the first three points that form a non-degenerate
2618 // triangle. If there are no such points then the path is
2619 // degenerate. The first is always point 0. Now we find the second
2620 // point.
2621 int i = 0;
2622 enum { kX = 0, kY = 1 };
2623 T v0[2];
2624 while (1) {
2625 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2626 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2627 if (v0[kX] || v0[kY]) {
2628 break;
2629 }
2630 if (++i == n - 1) {
2631 return false;
2632 }
2633 }
2634 // now find a third point that is not colinear with the first two
2635 // points and check the orientation of the triangle (which will be
2636 // the same as the orientation of the path).
2637 for (++i; i < n; ++i) {
2638 T v1[2];
2639 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2640 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2641 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2642 if (0 != cross) {
2643 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2644 return true;
2645 }
2646 }
2647 return false;
2648}
2649}
2650
reed@google.comac8543f2012-01-30 20:51:25 +00002651/*
2652 * We loop through all contours, and keep the computed cross-product of the
2653 * contour that contained the global y-max. If we just look at the first
2654 * contour, we may find one that is wound the opposite way (correctly) since
2655 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2656 * that is outer most (or at least has the global y-max) before we can consider
2657 * its cross product.
2658 */
reed@google.com69a99432012-01-10 18:00:10 +00002659bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002660// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002661 // don't want to pay the cost for computing this if it
2662 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002663
2664 if (kUnknown_Direction != fDirection) {
2665 *dir = static_cast<Direction>(fDirection);
2666 return true;
2667 }
reed@google.com69a99432012-01-10 18:00:10 +00002668 const Convexity conv = this->getConvexityOrUnknown();
2669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002670 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002671
reed@google.comac8543f2012-01-30 20:51:25 +00002672 // initialize with our logical y-min
2673 SkScalar ymax = this->getBounds().fTop;
2674 SkScalar ymaxCross = 0;
2675
reed@google.com69a99432012-01-10 18:00:10 +00002676 for (; !iter.done(); iter.next()) {
2677 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002678 if (n < 3) {
2679 continue;
2680 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002681
reed@google.comcabaf1d2012-01-11 21:03:05 +00002682 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002683 SkScalar cross = 0;
2684 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002685 // We try first at scalar precision, and then again at double
2686 // precision. This is because the vectors computed between distant
2687 // points may lose too much precision.
2688 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002689 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002690 return true;
2691 }
2692 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002693 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002694 return true;
2695 } else {
2696 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002697 }
2698 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002699 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002700 if (pts[index].fY < ymax) {
2701 continue;
2702 }
2703
reed@google.comc1ea60a2012-01-31 15:15:36 +00002704 // If there is more than 1 distinct point at the y-max, we take the
2705 // x-min and x-max of them and just subtract to compute the dir.
2706 if (pts[(index + 1) % n].fY == pts[index].fY) {
2707 int maxIndex;
2708 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002709 if (minIndex == maxIndex) {
2710 goto TRY_CROSSPROD;
2711 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002712 SkASSERT(pts[minIndex].fY == pts[index].fY);
2713 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2714 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2715 // we just subtract the indices, and let that auto-convert to
2716 // SkScalar, since we just want - or + to signal the direction.
2717 cross = minIndex - maxIndex;
2718 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002719 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002720 // Find a next and prev index to use for the cross-product test,
2721 // but we try to find pts that form non-zero vectors from pts[index]
2722 //
2723 // Its possible that we can't find two non-degenerate vectors, so
2724 // we have to guard our search (e.g. all the pts could be in the
2725 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002726
reed@google.comc1ea60a2012-01-31 15:15:36 +00002727 // we pass n - 1 instead of -1 so we don't foul up % operator by
2728 // passing it a negative LH argument.
2729 int prev = find_diff_pt(pts, index, n, n - 1);
2730 if (prev == index) {
2731 // completely degenerate, skip to next contour
2732 continue;
2733 }
2734 int next = find_diff_pt(pts, index, n, 1);
2735 SkASSERT(next != index);
2736 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002737 // if we get a zero and the points are horizontal, then we look at the spread in
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002738 // x-direction. We really should continue to walk away from the degeneracy until
2739 // there is a divergence.
2740 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002741 // construct the subtract so we get the correct Direction below
2742 cross = pts[index].fX - pts[next].fX;
2743 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002744 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002745
reed@google.comac8543f2012-01-30 20:51:25 +00002746 if (cross) {
2747 // record our best guess so far
2748 ymax = pts[index].fY;
2749 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002750 }
reed@google.com69a99432012-01-10 18:00:10 +00002751 }
2752 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002753 if (ymaxCross) {
2754 crossToDir(ymaxCross, dir);
2755 fDirection = *dir;
2756 return true;
2757 } else {
2758 return false;
2759 }
reed@google.comac8543f2012-01-30 20:51:25 +00002760}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002761
2762///////////////////////////////////////////////////////////////////////////////
2763
2764static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2765 SkScalar D, SkScalar t) {
2766 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2767}
2768
2769static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2770 SkScalar t) {
2771 SkScalar A = c3 + 3*(c1 - c2) - c0;
2772 SkScalar B = 3*(c2 - c1 - c1 + c0);
2773 SkScalar C = 3*(c1 - c0);
2774 SkScalar D = c0;
2775 return eval_cubic_coeff(A, B, C, D, t);
2776}
2777
2778/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2779 t value such that cubic(t) = target
2780 */
2781static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2782 SkScalar target, SkScalar* t) {
2783 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2784 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 SkScalar D = c0 - target;
2787 SkScalar A = c3 + 3*(c1 - c2) - c0;
2788 SkScalar B = 3*(c2 - c1 - c1 + c0);
2789 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002790
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2792 SkScalar minT = 0;
2793 SkScalar maxT = SK_Scalar1;
2794 SkScalar mid;
2795 int i;
2796 for (i = 0; i < 16; i++) {
2797 mid = SkScalarAve(minT, maxT);
2798 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2799 if (delta < 0) {
2800 minT = mid;
2801 delta = -delta;
2802 } else {
2803 maxT = mid;
2804 }
2805 if (delta < TOLERANCE) {
2806 break;
2807 }
2808 }
2809 *t = mid;
2810 return true;
2811}
2812
2813template <size_t N> static void find_minmax(const SkPoint pts[],
2814 SkScalar* minPtr, SkScalar* maxPtr) {
2815 SkScalar min, max;
2816 min = max = pts[0].fX;
2817 for (size_t i = 1; i < N; ++i) {
2818 min = SkMinScalar(min, pts[i].fX);
2819 max = SkMaxScalar(max, pts[i].fX);
2820 }
2821 *minPtr = min;
2822 *maxPtr = max;
2823}
2824
2825static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2826 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002827
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 int dir = 1;
2829 if (pts[0].fY > pts[3].fY) {
2830 storage[0] = pts[3];
2831 storage[1] = pts[2];
2832 storage[2] = pts[1];
2833 storage[3] = pts[0];
2834 pts = storage;
2835 dir = -1;
2836 }
2837 if (y < pts[0].fY || y >= pts[3].fY) {
2838 return 0;
2839 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 // quickreject or quickaccept
2842 SkScalar min, max;
2843 find_minmax<4>(pts, &min, &max);
2844 if (x < min) {
2845 return 0;
2846 }
2847 if (x > max) {
2848 return dir;
2849 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002850
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851 // compute the actual x(t) value
2852 SkScalar t, xt;
2853 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2854 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2855 } else {
2856 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2857 xt = y < mid ? pts[0].fX : pts[3].fX;
2858 }
2859 return xt < x ? dir : 0;
2860}
2861
2862static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2863 SkPoint dst[10];
2864 int n = SkChopCubicAtYExtrema(pts, dst);
2865 int w = 0;
2866 for (int i = 0; i <= n; ++i) {
2867 w += winding_mono_cubic(&dst[i * 3], x, y);
2868 }
2869 return w;
2870}
2871
2872static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2873 SkScalar y0 = pts[0].fY;
2874 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002875
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002876 int dir = 1;
2877 if (y0 > y2) {
2878 SkTSwap(y0, y2);
2879 dir = -1;
2880 }
2881 if (y < y0 || y >= y2) {
2882 return 0;
2883 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 // bounds check on X (not required. is it faster?)
2886#if 0
2887 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2888 return 0;
2889 }
2890#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002891
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 SkScalar roots[2];
2893 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2894 2 * (pts[1].fY - pts[0].fY),
2895 pts[0].fY - y,
2896 roots);
2897 SkASSERT(n <= 1);
2898 SkScalar xt;
2899 if (0 == n) {
2900 SkScalar mid = SkScalarAve(y0, y2);
2901 // Need [0] and [2] if dir == 1
2902 // and [2] and [0] if dir == -1
2903 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2904 } else {
2905 SkScalar t = roots[0];
2906 SkScalar C = pts[0].fX;
2907 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2908 SkScalar B = 2 * (pts[1].fX - C);
2909 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2910 }
2911 return xt < x ? dir : 0;
2912}
2913
2914static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2915 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2916 if (y0 == y1) {
2917 return true;
2918 }
2919 if (y0 < y1) {
2920 return y1 <= y2;
2921 } else {
2922 return y1 >= y2;
2923 }
2924}
2925
2926static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2927 SkPoint dst[5];
2928 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002929
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002930 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2931 n = SkChopQuadAtYExtrema(pts, dst);
2932 pts = dst;
2933 }
2934 int w = winding_mono_quad(pts, x, y);
2935 if (n > 0) {
2936 w += winding_mono_quad(&pts[2], x, y);
2937 }
2938 return w;
2939}
2940
2941static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2942 SkScalar x0 = pts[0].fX;
2943 SkScalar y0 = pts[0].fY;
2944 SkScalar x1 = pts[1].fX;
2945 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002946
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002947 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002948
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 int dir = 1;
2950 if (y0 > y1) {
2951 SkTSwap(y0, y1);
2952 dir = -1;
2953 }
2954 if (y < y0 || y >= y1) {
2955 return 0;
2956 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002957
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002958 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2959 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002960
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002961 if (SkScalarSignAsInt(cross) == dir) {
2962 dir = 0;
2963 }
2964 return dir;
2965}
2966
2967bool SkPath::contains(SkScalar x, SkScalar y) const {
2968 bool isInverse = this->isInverseFillType();
2969 if (this->isEmpty()) {
2970 return isInverse;
2971 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002972
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002973 const SkRect& bounds = this->getBounds();
2974 if (!bounds.contains(x, y)) {
2975 return isInverse;
2976 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002977
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002978 SkPath::Iter iter(*this, true);
2979 bool done = false;
2980 int w = 0;
2981 do {
2982 SkPoint pts[4];
2983 switch (iter.next(pts, false)) {
2984 case SkPath::kMove_Verb:
2985 case SkPath::kClose_Verb:
2986 break;
2987 case SkPath::kLine_Verb:
2988 w += winding_line(pts, x, y);
2989 break;
2990 case SkPath::kQuad_Verb:
2991 w += winding_quad(pts, x, y);
2992 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002993 case SkPath::kConic_Verb:
2994 SkASSERT(0);
2995 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 case SkPath::kCubic_Verb:
2997 w += winding_cubic(pts, x, y);
2998 break;
2999 case SkPath::kDone_Verb:
3000 done = true;
3001 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003002 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 switch (this->getFillType()) {
3006 case SkPath::kEvenOdd_FillType:
3007 case SkPath::kInverseEvenOdd_FillType:
3008 w &= 1;
3009 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003010 default:
3011 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 }
3013 return SkToBool(w);
3014}