blob: 97204bba01334d8b01331ba14c6be738447f355a [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;
djsollen@google.come63793a2012-03-21 15:39:03 +0000183 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000184#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000188 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000189 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000190#ifdef SK_BUILD_FOR_ANDROID
191 fGenerationID = that.fGenerationID;
192 fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
193#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195}
196
197SkPath::~SkPath() {
198 SkDEBUGCODE(this->validate();)
199}
200
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201SkPath& SkPath::operator=(const SkPath& that) {
202 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 if (this != &that) {
205 fPathRef.reset(SkRef(that.fPathRef.get()));
206 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000207#ifdef SK_BUILD_FOR_ANDROID
208 GEN_ID_INC; // Similar to swap, we can't just copy this or it could go back in time.
209 fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
210#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211 }
212 SkDEBUGCODE(this->validate();)
213 return *this;
214}
215
bungeman@google.coma5809a32013-06-21 15:13:34 +0000216void SkPath::copyFields(const SkPath& that) {
217 //fPathRef is assumed to have been set by the caller.
218 fBounds = that.fBounds;
219 fLastMoveToIndex = that.fLastMoveToIndex;
220 fFillType = that.fFillType;
221 fSegmentMask = that.fSegmentMask;
222 fBoundsIsDirty = that.fBoundsIsDirty;
223 fConvexity = that.fConvexity;
224 fDirection = that.fDirection;
225 fIsFinite = that.fIsFinite;
226 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000227}
228
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000229bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000230 // note: don't need to look at isConvex or bounds, since just comparing the
231 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000232
233 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
234 // since it is only a cache of info in the fVerbs, but its a fast way to
235 // notice a difference
236
reed@android.com3abec1d2009-03-02 05:36:20 +0000237 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000238 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000239 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000240}
241
bungeman@google.coma5809a32013-06-21 15:13:34 +0000242void SkPath::swap(SkPath& that) {
243 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
bungeman@google.coma5809a32013-06-21 15:13:34 +0000245 if (this != &that) {
246 fPathRef.swap(&that.fPathRef);
247 SkTSwap<SkRect>(fBounds, that.fBounds);
248 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
249 SkTSwap<uint8_t>(fFillType, that.fFillType);
250 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
251 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
252 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
253 SkTSwap<uint8_t>(fDirection, that.fDirection);
254 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
255 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000256#ifdef SK_BUILD_FOR_ANDROID
257 // It doesn't really make sense to swap the generation IDs here, because they might go
258 // backwards. To be safe we increment both to mark them both as changed.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000259 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000260 GEN_ID_PTR_INC(&that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000261 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
262#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 }
264}
265
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266static inline bool check_edge_against_rect(const SkPoint& p0,
267 const SkPoint& p1,
268 const SkRect& rect,
269 SkPath::Direction dir) {
270 const SkPoint* edgeBegin;
271 SkVector v;
272 if (SkPath::kCW_Direction == dir) {
273 v = p1 - p0;
274 edgeBegin = &p0;
275 } else {
276 v = p0 - p1;
277 edgeBegin = &p1;
278 }
279 if (v.fX || v.fY) {
280 // check the cross product of v with the vec from edgeBegin to each rect corner
281 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
282 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
283 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
284 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
285 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
286 return false;
287 }
288 }
289 return true;
290}
291
292bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
293 // This only handles non-degenerate convex paths currently.
294 if (kConvex_Convexity != this->getConvexity()) {
295 return false;
296 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000297
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 Direction direction;
299 if (!this->cheapComputeDirection(&direction)) {
300 return false;
301 }
302
303 SkPoint firstPt;
304 SkPoint prevPt;
305 RawIter iter(*this);
306 SkPath::Verb verb;
307 SkPoint pts[4];
308 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000309 SkDEBUGCODE(int segmentCount = 0;)
310 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311
312 while ((verb = iter.next(pts)) != kDone_Verb) {
313 int nextPt = -1;
314 switch (verb) {
315 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000316 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000317 SkDEBUGCODE(++moveCnt);
318 firstPt = prevPt = pts[0];
319 break;
320 case kLine_Verb:
321 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000322 SkASSERT(moveCnt && !closeCount);
323 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000324 break;
325 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000326 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000327 SkASSERT(moveCnt && !closeCount);
328 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000329 nextPt = 2;
330 break;
331 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000332 SkASSERT(moveCnt && !closeCount);
333 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000334 nextPt = 3;
335 break;
336 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000337 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000338 break;
339 default:
340 SkDEBUGFAIL("unknown verb");
341 }
342 if (-1 != nextPt) {
343 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
344 return false;
345 }
346 prevPt = pts[nextPt];
347 }
348 }
349
350 return check_edge_against_rect(prevPt, firstPt, rect, direction);
351}
352
djsollen@google.com56c69772011-11-08 19:00:26 +0000353#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000354uint32_t SkPath::getGenerationID() const {
355 return fGenerationID;
356}
djsollen@google.come63793a2012-03-21 15:39:03 +0000357
358const SkPath* SkPath::getSourcePath() const {
359 return fSourcePath;
360}
361
362void SkPath::setSourcePath(const SkPath* path) {
363 fSourcePath = path;
364}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000365#endif
366
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367void SkPath::reset() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
374void SkPath::rewind() {
375 SkDEBUGCODE(this->validate();)
376
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000377 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000378 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
381bool SkPath::isEmpty() const {
382 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384}
385
reed@google.com7e6c4d12012-05-10 14:05:43 +0000386bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000387 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000388
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000389 if (2 == verbCount) {
390 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
391 if (kLine_Verb == fPathRef->atVerb(1)) {
392 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000393 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000394 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000395 line[0] = pts[0];
396 line[1] = pts[1];
397 }
398 return true;
399 }
400 }
401 return false;
402}
403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404/*
405 Determines if path is a rect by keeping track of changes in direction
406 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000407
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 The direction is computed such that:
409 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000410 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000412 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000413
caryclark@google.comf1316942011-07-26 19:54:45 +0000414A rectangle cycles up/right/down/left or up/left/down/right.
415
416The test fails if:
417 The path is closed, and followed by a line.
418 A second move creates a new endpoint.
419 A diagonal line is parsed.
420 There's more than four changes of direction.
421 There's a discontinuity on the line (e.g., a move in the middle)
422 The line reverses direction.
423 The rectangle doesn't complete a cycle.
424 The path contains a quadratic or cubic.
425 The path contains fewer than four points.
426 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428It's OK if the path has:
429 Several colinear line segments composing a rectangle side.
430 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000431
caryclark@google.comf1316942011-07-26 19:54:45 +0000432The direction takes advantage of the corners found since opposite sides
433must travel in opposite directions.
434
435FIXME: Allow colinear quads and cubics to be treated like lines.
436FIXME: If the API passes fill-only, return true if the filled stroke
437 is a rectangle, though the caller failed to close the path.
438 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000439bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
440 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 int corners = 0;
442 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000444 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000445 first.set(0, 0);
446 last.set(0, 0);
447 int firstDirection = 0;
448 int lastDirection = 0;
449 int nextDirection = 0;
450 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000452 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000453 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
454 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000456 savePts = pts;
457 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 autoClose = true;
459 case kLine_Verb: {
460 SkScalar left = last.fX;
461 SkScalar top = last.fY;
462 SkScalar right = pts->fX;
463 SkScalar bottom = pts->fY;
464 ++pts;
465 if (left != right && top != bottom) {
466 return false; // diagonal
467 }
468 if (left == right && top == bottom) {
469 break; // single point on side OK
470 }
471 nextDirection = (left != right) << 0 |
472 (left < right || top < bottom) << 1;
473 if (0 == corners) {
474 firstDirection = nextDirection;
475 first = last;
476 last = pts[-1];
477 corners = 1;
478 closedOrMoved = false;
479 break;
480 }
481 if (closedOrMoved) {
482 return false; // closed followed by a line
483 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000484 if (autoClose && nextDirection == firstDirection) {
485 break; // colinear with first
486 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 closedOrMoved = autoClose;
488 if (lastDirection != nextDirection) {
489 if (++corners > 4) {
490 return false; // too many direction changes
491 }
492 }
493 last = pts[-1];
494 if (lastDirection == nextDirection) {
495 break; // colinear segment
496 }
497 // Possible values for corners are 2, 3, and 4.
498 // When corners == 3, nextDirection opposes firstDirection.
499 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000500 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000501 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
502 if ((directionCycle ^ turn) != nextDirection) {
503 return false; // direction didn't follow cycle
504 }
505 break;
506 }
507 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000508 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 case kCubic_Verb:
510 return false; // quadratic, cubic not allowed
511 case kMove_Verb:
512 last = *pts++;
513 closedOrMoved = true;
514 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000515 default:
516 SkASSERT(!"unexpected verb");
517 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000519 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000520 lastDirection = nextDirection;
521 }
522 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000523 bool result = 4 == corners && (first == last || autoClose);
524 if (savePts) {
525 *ptsPtr = savePts;
526 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000527 if (result && isClosed) {
528 *isClosed = autoClose;
529 }
530 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000531 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000532 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 return result;
534}
535
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000536bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000537 SkDEBUGCODE(this->validate();)
538 int currVerb = 0;
539 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000540 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
541 if (result && rect) {
542 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000543 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000544 return result;
545}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000546
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000547bool SkPath::isRect(bool* isClosed, Direction* direction) const {
548 SkDEBUGCODE(this->validate();)
549 int currVerb = 0;
550 const SkPoint* pts = fPathRef->points();
551 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000552}
553
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000555 SkDEBUGCODE(this->validate();)
556 int currVerb = 0;
557 const SkPoint* pts = fPathRef->points();
558 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000559 Direction testDirs[2];
560 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return false;
562 }
563 const SkPoint* last = pts;
564 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000565 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000566 testRects[0].set(first, SkToS32(last - first));
567 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 if (testRects[0].contains(testRects[1])) {
569 if (rects) {
570 rects[0] = testRects[0];
571 rects[1] = testRects[1];
572 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000573 if (dirs) {
574 dirs[0] = testDirs[0];
575 dirs[1] = testDirs[1];
576 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000577 return true;
578 }
579 if (testRects[1].contains(testRects[0])) {
580 if (rects) {
581 rects[0] = testRects[1];
582 rects[1] = testRects[0];
583 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000584 if (dirs) {
585 dirs[0] = testDirs[1];
586 dirs[1] = testDirs[0];
587 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 return true;
589 }
590 }
591 return false;
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countPoints() const {
595 return fPathRef->countPoints();
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countPoints());
604 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
605 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606}
607
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000608SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000609 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
610 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000611 }
612 return SkPoint::Make(0, 0);
613}
614
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000615int SkPath::countVerbs() const {
616 return fPathRef->countVerbs();
617}
618
619static inline void copy_verbs_reverse(uint8_t* inorderDst,
620 const uint8_t* reversedSrc,
621 int count) {
622 for (int i = 0; i < count; ++i) {
623 inorderDst[i] = reversedSrc[~i];
624 }
625}
626
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000627int SkPath::getVerbs(uint8_t dst[], int max) const {
628 SkDEBUGCODE(this->validate();)
629
630 SkASSERT(max >= 0);
631 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 int count = SkMin32(max, fPathRef->countVerbs());
633 copy_verbs_reverse(dst, fPathRef->verbs(), count);
634 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000635}
636
reed@google.com294dd7b2011-10-11 11:58:32 +0000637bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000641 if (count > 0) {
642 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000645 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000647 if (lastPt) {
648 lastPt->set(0, 0);
649 }
650 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651}
652
653void SkPath::setLastPt(SkScalar x, SkScalar y) {
654 SkDEBUGCODE(this->validate();)
655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 if (count == 0) {
658 this->moveTo(x, y);
659 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000660 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor ed(&fPathRef);
662 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000663 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 }
665}
666
reed@android.comd252db02009-04-01 18:31:44 +0000667void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000669 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000672 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673}
674
reed@google.com04863fa2011-05-15 04:08:24 +0000675void SkPath::setConvexity(Convexity c) {
676 if (fConvexity != c) {
677 fConvexity = c;
678 GEN_ID_INC;
679 }
680}
681
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682//////////////////////////////////////////////////////////////////////////////
683// Construction methods
684
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000685#define DIRTY_AFTER_EDIT \
686 do { \
687 fBoundsIsDirty = true; \
688 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000689 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000690 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000691 } while (0)
692
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000693#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
694 do { \
695 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000696 } while (0)
697
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698void SkPath::incReserve(U16CPU inc) {
699 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000700 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701 SkDEBUGCODE(this->validate();)
702}
703
704void SkPath::moveTo(SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708
reed@google.comd335d1d2012-01-12 18:17:11 +0000709 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000711
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000714 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000715 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716}
717
718void SkPath::rMoveTo(SkScalar x, SkScalar y) {
719 SkPoint pt;
720 this->getLastPt(&pt);
721 this->moveTo(pt.fX + x, pt.fY + y);
722}
723
reed@google.comd335d1d2012-01-12 18:17:11 +0000724void SkPath::injectMoveToIfNeeded() {
725 if (fLastMoveToIndex < 0) {
726 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000728 x = y = 0;
729 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000730 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000731 x = pt.fX;
732 y = pt.fY;
733 }
734 this->moveTo(x, y);
735 }
736}
737
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738void SkPath::lineTo(SkScalar x, SkScalar y) {
739 SkDEBUGCODE(this->validate();)
740
reed@google.comd335d1d2012-01-12 18:17:11 +0000741 this->injectMoveToIfNeeded();
742
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000743 SkPathRef::Editor ed(&fPathRef);
744 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000745 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000747 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000748 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749}
750
751void SkPath::rLineTo(SkScalar x, SkScalar y) {
752 SkPoint pt;
753 this->getLastPt(&pt);
754 this->lineTo(pt.fX + x, pt.fY + y);
755}
756
757void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
758 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000759
reed@google.comd335d1d2012-01-12 18:17:11 +0000760 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000761
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000762 SkPathRef::Editor ed(&fPathRef);
763 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000766 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000767
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000768 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000769 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770}
771
772void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
773 SkPoint pt;
774 this->getLastPt(&pt);
775 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
776}
777
reed@google.com277c3f82013-05-31 15:17:50 +0000778void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
779 SkScalar w) {
780 // check for <= 0 or NaN with this test
781 if (!(w > 0)) {
782 this->lineTo(x2, y2);
783 } else if (!SkScalarIsFinite(w)) {
784 this->lineTo(x1, y1);
785 this->lineTo(x2, y2);
786 } else if (SK_Scalar1 == w) {
787 this->quadTo(x1, y1, x2, y2);
788 } else {
789 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000790
reed@google.com277c3f82013-05-31 15:17:50 +0000791 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
reed@google.com277c3f82013-05-31 15:17:50 +0000793 SkPathRef::Editor ed(&fPathRef);
794 SkPoint* pts = ed.growForConic(w);
795 pts[0].set(x1, y1);
796 pts[1].set(x2, y2);
797 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000798
reed@google.com277c3f82013-05-31 15:17:50 +0000799 GEN_ID_INC;
800 DIRTY_AFTER_EDIT;
801 }
802}
803
804void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
805 SkScalar w) {
806 SkPoint pt;
807 this->getLastPt(&pt);
808 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
809}
810
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
812 SkScalar x3, SkScalar y3) {
813 SkDEBUGCODE(this->validate();)
814
reed@google.comd335d1d2012-01-12 18:17:11 +0000815 this->injectMoveToIfNeeded();
816
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 SkPathRef::Editor ed(&fPathRef);
818 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819 pts[0].set(x1, y1);
820 pts[1].set(x2, y2);
821 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000822 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000824 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000825 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826}
827
828void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
829 SkScalar x3, SkScalar y3) {
830 SkPoint pt;
831 this->getLastPt(&pt);
832 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
833 pt.fX + x3, pt.fY + y3);
834}
835
836void SkPath::close() {
837 SkDEBUGCODE(this->validate();)
838
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000839 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 case kLine_Verb:
843 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000844 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 case kMove_Verb: {
847 SkPathRef::Editor ed(&fPathRef);
848 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000849 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 }
reed@google.com277c3f82013-05-31 15:17:50 +0000852 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000853 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000854 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000855 default:
856 SkASSERT(!"unexpected verb");
857 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 }
859 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000860
861 // signal that we need a moveTo to follow us (unless we're done)
862#if 0
863 if (fLastMoveToIndex >= 0) {
864 fLastMoveToIndex = ~fLastMoveToIndex;
865 }
866#else
867 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
868#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869}
870
871///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000872
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000873static void assert_known_direction(int dir) {
874 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
875}
876
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877void SkPath::addRect(const SkRect& rect, Direction dir) {
878 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
879}
880
881void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
882 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000883 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000884 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
885 SkAutoDisableDirectionCheck addc(this);
886
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
888
889 this->incReserve(5);
890
891 this->moveTo(left, top);
892 if (dir == kCCW_Direction) {
893 this->lineTo(left, bottom);
894 this->lineTo(right, bottom);
895 this->lineTo(right, top);
896 } else {
897 this->lineTo(right, top);
898 this->lineTo(right, bottom);
899 this->lineTo(left, bottom);
900 }
901 this->close();
902}
903
reed@google.com744faba2012-05-29 19:54:52 +0000904void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
905 SkDEBUGCODE(this->validate();)
906 if (count <= 0) {
907 return;
908 }
909
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000910 SkPathRef::Editor ed(&fPathRef);
911 fLastMoveToIndex = ed.pathRef()->countPoints();
912 uint8_t* vb;
913 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000914 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000915 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000916
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000917 memcpy(p, pts, count * sizeof(SkPoint));
918 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000919 if (count > 1) {
920 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
921 // be 0, the compiler will remove the test/branch entirely.
922 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000923 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000924 } else {
925 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000926 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000927 }
928 }
929 fSegmentMask |= kLine_SegmentMask;
930 }
931 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000932 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000933 }
934
935 GEN_ID_INC;
936 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000937 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000938}
939
reed@android.com8a1c16f2008-12-17 15:59:43 +0000940static void add_corner_arc(SkPath* path, const SkRect& rect,
941 SkScalar rx, SkScalar ry, int startAngle,
942 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000943 // These two asserts are not sufficient, since really we want to know
944 // that the pair of radii (e.g. left and right, or top and bottom) sum
945 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000946 // these conservative asserts.
947 SkASSERT(0 <= rx && rx <= rect.width());
948 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000949
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 SkRect r;
951 r.set(-rx, -ry, rx, ry);
952
953 switch (startAngle) {
954 case 0:
955 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
956 break;
957 case 90:
958 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
959 break;
960 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
961 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000962 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963 }
reed@google.comabf15c12011-01-18 20:35:51 +0000964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965 SkScalar start = SkIntToScalar(startAngle);
966 SkScalar sweep = SkIntToScalar(90);
967 if (SkPath::kCCW_Direction == dir) {
968 start += sweep;
969 sweep = -sweep;
970 }
reed@google.comabf15c12011-01-18 20:35:51 +0000971
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 path->arcTo(r, start, sweep, forceMoveTo);
973}
974
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000975void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000977 SkRRect rrect;
978 rrect.setRectRadii(rect, (const SkVector*) radii);
979 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980}
981
reed@google.com4ed0fb72012-12-12 20:48:18 +0000982void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000983 assert_known_direction(dir);
984
985 if (rrect.isEmpty()) {
986 return;
987 }
988
reed@google.com4ed0fb72012-12-12 20:48:18 +0000989 const SkRect& bounds = rrect.getBounds();
990
991 if (rrect.isRect()) {
992 this->addRect(bounds, dir);
993 } else if (rrect.isOval()) {
994 this->addOval(bounds, dir);
995 } else if (rrect.isSimple()) {
996 const SkVector& rad = rrect.getSimpleRadii();
997 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
998 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000999 SkAutoPathBoundsUpdate apbu(this, bounds);
1000
1001 if (kCW_Direction == dir) {
1002 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1003 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1004 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1005 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1006 } else {
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[3].fX, rrect.fRadii[3].fY, 90, 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[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1011 }
1012 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001013 }
1014}
1015
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001016bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001017 int count = fPathRef->countVerbs();
1018 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1019 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001020 if (*verbs == kLine_Verb ||
1021 *verbs == kQuad_Verb ||
1022 *verbs == kCubic_Verb) {
1023 return false;
1024 }
1025 ++verbs;
1026 }
1027 return true;
1028}
1029
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001030#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1031
1032void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1033 Direction dir) {
1034 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001035
humper@google.com75e3ca12013-04-08 21:44:11 +00001036 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001037 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001038 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001039 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001040 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1041 return;
1042 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001043
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001044 SkScalar w = rect.width();
1045 SkScalar halfW = SkScalarHalf(w);
1046 SkScalar h = rect.height();
1047 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001048
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001049 if (halfW <= 0 || halfH <= 0) {
1050 return;
1051 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001052
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001053 bool skip_hori = rx >= halfW;
1054 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001055
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001056 if (skip_hori && skip_vert) {
1057 this->addOval(rect, dir);
1058 return;
1059 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001060
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001061 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001062
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001063 SkAutoPathBoundsUpdate apbu(this, rect);
1064 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001065
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001066 if (skip_hori) {
1067 rx = halfW;
1068 } else if (skip_vert) {
1069 ry = halfH;
1070 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001071
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001072 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1073 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001074
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001075 this->incReserve(17);
1076 this->moveTo(rect.fRight - rx, rect.fTop);
1077 if (dir == kCCW_Direction) {
1078 if (!skip_hori) {
1079 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1080 }
1081 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1082 rect.fLeft, rect.fTop + ry - sy,
1083 rect.fLeft, rect.fTop + ry); // top-left
1084 if (!skip_vert) {
1085 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1086 }
1087 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1088 rect.fLeft + rx - sx, rect.fBottom,
1089 rect.fLeft + rx, rect.fBottom); // bot-left
1090 if (!skip_hori) {
1091 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1092 }
1093 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1094 rect.fRight, rect.fBottom - ry + sy,
1095 rect.fRight, rect.fBottom - ry); // bot-right
1096 if (!skip_vert) {
1097 this->lineTo(rect.fRight, rect.fTop + ry);
1098 }
1099 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1100 rect.fRight - rx + sx, rect.fTop,
1101 rect.fRight - rx, rect.fTop); // top-right
1102 } else {
1103 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1104 rect.fRight, rect.fTop + ry - sy,
1105 rect.fRight, rect.fTop + ry); // top-right
1106 if (!skip_vert) {
1107 this->lineTo(rect.fRight, rect.fBottom - ry);
1108 }
1109 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1110 rect.fRight - rx + sx, rect.fBottom,
1111 rect.fRight - rx, rect.fBottom); // bot-right
1112 if (!skip_hori) {
1113 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1114 }
1115 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1116 rect.fLeft, rect.fBottom - ry + sy,
1117 rect.fLeft, rect.fBottom - ry); // bot-left
1118 if (!skip_vert) {
1119 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1120 }
1121 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1122 rect.fLeft + rx - sx, rect.fTop,
1123 rect.fLeft + rx, rect.fTop); // top-left
1124 if (!skip_hori) {
1125 this->lineTo(rect.fRight - rx, rect.fTop); // top
1126 }
1127 }
1128 this->close();
1129}
1130
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001132 assert_known_direction(dir);
1133
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001134 /* If addOval() is called after previous moveTo(),
1135 this path is still marked as an oval. This is used to
1136 fit into WebKit's calling sequences.
1137 We can't simply check isEmpty() in this case, as additional
1138 moveTo() would mark the path non empty.
1139 */
1140 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001141 if (fIsOval) {
1142 fDirection = dir;
1143 } else {
1144 fDirection = kUnknown_Direction;
1145 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001146
1147 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001148 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001149
reed@android.com8a1c16f2008-12-17 15:59:43 +00001150 SkAutoPathBoundsUpdate apbu(this, oval);
1151
1152 SkScalar cx = oval.centerX();
1153 SkScalar cy = oval.centerY();
1154 SkScalar rx = SkScalarHalf(oval.width());
1155 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1158 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1159 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1160 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1161
1162 /*
1163 To handle imprecision in computing the center and radii, we revert to
1164 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1165 to ensure that we don't exceed the oval's bounds *ever*, since we want
1166 to use oval for our fast-bounds, rather than have to recompute it.
1167 */
1168 const SkScalar L = oval.fLeft; // cx - rx
1169 const SkScalar T = oval.fTop; // cy - ry
1170 const SkScalar R = oval.fRight; // cx + rx
1171 const SkScalar B = oval.fBottom; // cy + ry
1172
1173 this->incReserve(17); // 8 quads + close
1174 this->moveTo(R, cy);
1175 if (dir == kCCW_Direction) {
1176 this->quadTo( R, cy - sy, cx + mx, cy - my);
1177 this->quadTo(cx + sx, T, cx , T);
1178 this->quadTo(cx - sx, T, cx - mx, cy - my);
1179 this->quadTo( L, cy - sy, L, cy );
1180 this->quadTo( L, cy + sy, cx - mx, cy + my);
1181 this->quadTo(cx - sx, B, cx , B);
1182 this->quadTo(cx + sx, B, cx + mx, cy + my);
1183 this->quadTo( R, cy + sy, R, cy );
1184 } else {
1185 this->quadTo( R, 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( L, cy + sy, L, cy );
1189 this->quadTo( L, cy - sy, cx - mx, cy - my);
1190 this->quadTo(cx - sx, T, cx , T);
1191 this->quadTo(cx + sx, T, cx + mx, cy - my);
1192 this->quadTo( R, cy - sy, R, cy );
1193 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 this->close();
1195}
1196
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197bool SkPath::isOval(SkRect* rect) const {
1198 if (fIsOval && rect) {
1199 *rect = getBounds();
1200 }
1201
1202 return fIsOval;
1203}
1204
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1206 if (r > 0) {
1207 SkRect rect;
1208 rect.set(x - r, y - r, x + r, y + r);
1209 this->addOval(rect, dir);
1210 }
1211}
1212
1213#include "SkGeometry.h"
1214
1215static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1216 SkScalar sweepAngle,
1217 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001218
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001219 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001220 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1221 // Chrome uses this path to move into and out of ovals. If not
1222 // treated as a special case the moves can distort the oval's
1223 // bounding box (and break the circle special case).
1224 pts[0].set(oval.fRight, oval.centerY());
1225 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001226 } else if (0 == oval.width() && 0 == oval.height()) {
1227 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001228 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001229 // a rect.
1230 // TODO: optimizing the case where only one of width or height is zero
1231 // should also be considered. This case, however, doesn't seem to be
1232 // as common as the single point case.
1233 pts[0].set(oval.fRight, oval.fTop);
1234 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001235 }
1236
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 SkVector start, stop;
1238
1239 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1240 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1241 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001242
1243 /* If the sweep angle is nearly (but less than) 360, then due to precision
1244 loss in radians-conversion and/or sin/cos, we may end up with coincident
1245 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1246 of drawing a nearly complete circle (good).
1247 e.g. canvas.drawArc(0, 359.99, ...)
1248 -vs- canvas.drawArc(0, 359.9, ...)
1249 We try to detect this edge case, and tweak the stop vector
1250 */
1251 if (start == stop) {
1252 SkScalar sw = SkScalarAbs(sweepAngle);
1253 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1254 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1255 // make a guess at a tiny angle (in radians) to tweak by
1256 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1257 // not sure how much will be enough, so we use a loop
1258 do {
1259 stopRad -= deltaRad;
1260 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1261 } while (start == stop);
1262 }
1263 }
1264
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001266
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1268 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 return SkBuildQuadArc(start, stop,
1271 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1272 &matrix, pts);
1273}
1274
1275void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1276 bool forceMoveTo) {
1277 if (oval.width() < 0 || oval.height() < 0) {
1278 return;
1279 }
1280
1281 SkPoint pts[kSkBuildQuadArcStorage];
1282 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1283 SkASSERT((count & 1) == 1);
1284
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001285 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 forceMoveTo = true;
1287 }
1288 this->incReserve(count);
1289 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1290 for (int i = 1; i < count; i += 2) {
1291 this->quadTo(pts[i], pts[i+1]);
1292 }
1293}
1294
1295void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1296 SkScalar sweepAngle) {
1297 if (oval.isEmpty() || 0 == sweepAngle) {
1298 return;
1299 }
1300
1301 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1302
1303 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1304 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1305 return;
1306 }
1307
1308 SkPoint pts[kSkBuildQuadArcStorage];
1309 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1310
1311 this->incReserve(count);
1312 this->moveTo(pts[0]);
1313 for (int i = 1; i < count; i += 2) {
1314 this->quadTo(pts[i], pts[i+1]);
1315 }
1316}
1317
1318/*
1319 Need to handle the case when the angle is sharp, and our computed end-points
1320 for the arc go behind pt1 and/or p2...
1321*/
1322void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1323 SkScalar radius) {
1324 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001325
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 // need to know our prev pt so we can construct tangent vectors
1327 {
1328 SkPoint start;
1329 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001330 // Handle degenerate cases by adding a line to the first point and
1331 // bailing out.
1332 if ((x1 == start.fX && y1 == start.fY) ||
1333 (x1 == x2 && y1 == y2) ||
1334 radius == 0) {
1335 this->lineTo(x1, y1);
1336 return;
1337 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 before.setNormalize(x1 - start.fX, y1 - start.fY);
1339 after.setNormalize(x2 - x1, y2 - y1);
1340 }
reed@google.comabf15c12011-01-18 20:35:51 +00001341
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkScalar cosh = SkPoint::DotProduct(before, after);
1343 SkScalar sinh = SkPoint::CrossProduct(before, after);
1344
1345 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001346 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 return;
1348 }
reed@google.comabf15c12011-01-18 20:35:51 +00001349
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1351 if (dist < 0) {
1352 dist = -dist;
1353 }
1354
1355 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1356 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1357 SkRotationDirection arcDir;
1358
1359 // now turn before/after into normals
1360 if (sinh > 0) {
1361 before.rotateCCW();
1362 after.rotateCCW();
1363 arcDir = kCW_SkRotationDirection;
1364 } else {
1365 before.rotateCW();
1366 after.rotateCW();
1367 arcDir = kCCW_SkRotationDirection;
1368 }
1369
1370 SkMatrix matrix;
1371 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001372
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 matrix.setScale(radius, radius);
1374 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1375 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001376
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001378
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379 this->incReserve(count);
1380 // [xx,yy] == pts[0]
1381 this->lineTo(xx, yy);
1382 for (int i = 1; i < count; i += 2) {
1383 this->quadTo(pts[i], pts[i+1]);
1384 }
1385}
1386
1387///////////////////////////////////////////////////////////////////////////////
1388
1389void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1390 SkMatrix matrix;
1391
1392 matrix.setTranslate(dx, dy);
1393 this->addPath(path, matrix);
1394}
1395
1396void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001397 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001399 fIsOval = false;
1400
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001401 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 SkPoint pts[4];
1403 Verb verb;
1404
1405 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1406
1407 while ((verb = iter.next(pts)) != kDone_Verb) {
1408 switch (verb) {
1409 case kMove_Verb:
1410 proc(matrix, &pts[0], &pts[0], 1);
1411 this->moveTo(pts[0]);
1412 break;
1413 case kLine_Verb:
1414 proc(matrix, &pts[1], &pts[1], 1);
1415 this->lineTo(pts[1]);
1416 break;
1417 case kQuad_Verb:
1418 proc(matrix, &pts[1], &pts[1], 2);
1419 this->quadTo(pts[1], pts[2]);
1420 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001421 case kConic_Verb:
1422 proc(matrix, &pts[1], &pts[1], 2);
1423 this->conicTo(pts[1], pts[2], iter.conicWeight());
1424 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 case kCubic_Verb:
1426 proc(matrix, &pts[1], &pts[1], 3);
1427 this->cubicTo(pts[1], pts[2], pts[3]);
1428 break;
1429 case kClose_Verb:
1430 this->close();
1431 break;
1432 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001433 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 }
1435 }
1436}
1437
1438///////////////////////////////////////////////////////////////////////////////
1439
reed@google.com277c3f82013-05-31 15:17:50 +00001440static int pts_in_verb(unsigned verb) {
1441 static const uint8_t gPtsInVerb[] = {
1442 1, // kMove
1443 1, // kLine
1444 2, // kQuad
1445 2, // kConic
1446 3, // kCubic
1447 0, // kClose
1448 0 // kDone
1449 };
1450
1451 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1452 return gPtsInVerb[verb];
1453}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454
1455// ignore the initial moveto, and stop when the 1st contour ends
1456void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001457 int i, vcount = path.fPathRef->countVerbs();
1458 // exit early if the path is empty, or just has a moveTo.
1459 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 return;
1461 }
1462
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001463 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001465 fIsOval = false;
1466
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001467 const uint8_t* verbs = path.fPathRef->verbs();
1468 // skip the initial moveTo
1469 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001470 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001474 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 case kLine_Verb:
1476 this->lineTo(pts[0].fX, pts[0].fY);
1477 break;
1478 case kQuad_Verb:
1479 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1480 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001481 case kConic_Verb:
1482 this->conicTo(pts[0], pts[1], *conicWeight++);
1483 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001485 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 +00001486 break;
1487 case kClose_Verb:
1488 return;
1489 }
reed@google.com277c3f82013-05-31 15:17:50 +00001490 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 }
1492}
1493
1494// ignore the last point of the 1st contour
1495void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001496 int i, vcount = path.fPathRef->countVerbs();
1497 // exit early if the path is empty, or just has a moveTo.
1498 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499 return;
1500 }
1501
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001502 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001504 fIsOval = false;
1505
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 const uint8_t* verbs = path.fPathRef->verbs();
1507 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001508 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001510 SkASSERT(verbs[~0] == kMove_Verb);
1511 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001512 unsigned v = verbs[~i];
1513 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 if (n == 0) {
1515 break;
1516 }
1517 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001518 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 }
1520
1521 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001522 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kLine_Verb:
1524 this->lineTo(pts[-1].fX, pts[-1].fY);
1525 break;
1526 case kQuad_Verb:
1527 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1528 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001529 case kConic_Verb:
1530 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1531 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 case kCubic_Verb:
1533 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1534 pts[-3].fX, pts[-3].fY);
1535 break;
1536 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001537 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 break;
1539 }
reed@google.com277c3f82013-05-31 15:17:50 +00001540 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 }
1542}
1543
reed@google.com63d73742012-01-10 15:33:12 +00001544void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001545 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001546
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 const SkPoint* pts = src.fPathRef->pointsEnd();
1548 // we will iterator through src's verbs backwards
1549 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1550 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001551 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001552
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001553 fIsOval = false;
1554
reed@google.com63d73742012-01-10 15:33:12 +00001555 bool needMove = true;
1556 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 while (verbs < verbsEnd) {
1558 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001559 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001560
1561 if (needMove) {
1562 --pts;
1563 this->moveTo(pts->fX, pts->fY);
1564 needMove = false;
1565 }
1566 pts -= n;
1567 switch (v) {
1568 case kMove_Verb:
1569 if (needClose) {
1570 this->close();
1571 needClose = false;
1572 }
1573 needMove = true;
1574 pts += 1; // so we see the point in "if (needMove)" above
1575 break;
1576 case kLine_Verb:
1577 this->lineTo(pts[0]);
1578 break;
1579 case kQuad_Verb:
1580 this->quadTo(pts[1], pts[0]);
1581 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001582 case kConic_Verb:
1583 this->conicTo(pts[1], pts[0], *--conicWeights);
1584 break;
reed@google.com63d73742012-01-10 15:33:12 +00001585 case kCubic_Verb:
1586 this->cubicTo(pts[2], pts[1], pts[0]);
1587 break;
1588 case kClose_Verb:
1589 needClose = true;
1590 break;
1591 default:
1592 SkASSERT(!"unexpected verb");
1593 }
1594 }
1595}
1596
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597///////////////////////////////////////////////////////////////////////////////
1598
1599void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1600 SkMatrix matrix;
1601
1602 matrix.setTranslate(dx, dy);
1603 this->transform(matrix, dst);
1604}
1605
1606#include "SkGeometry.h"
1607
1608static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1609 int level = 2) {
1610 if (--level >= 0) {
1611 SkPoint tmp[5];
1612
1613 SkChopQuadAtHalf(pts, tmp);
1614 subdivide_quad_to(path, &tmp[0], level);
1615 subdivide_quad_to(path, &tmp[2], level);
1616 } else {
1617 path->quadTo(pts[1], pts[2]);
1618 }
1619}
1620
1621static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1622 int level = 2) {
1623 if (--level >= 0) {
1624 SkPoint tmp[7];
1625
1626 SkChopCubicAtHalf(pts, tmp);
1627 subdivide_cubic_to(path, &tmp[0], level);
1628 subdivide_cubic_to(path, &tmp[3], level);
1629 } else {
1630 path->cubicTo(pts[1], pts[2], pts[3]);
1631 }
1632}
1633
1634void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1635 SkDEBUGCODE(this->validate();)
1636 if (dst == NULL) {
1637 dst = (SkPath*)this;
1638 }
1639
tomhudson@google.com8d430182011-06-06 19:11:19 +00001640 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 SkPath tmp;
1642 tmp.fFillType = fFillType;
1643
1644 SkPath::Iter iter(*this, false);
1645 SkPoint pts[4];
1646 SkPath::Verb verb;
1647
reed@google.com4a3b7142012-05-16 17:16:46 +00001648 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 switch (verb) {
1650 case kMove_Verb:
1651 tmp.moveTo(pts[0]);
1652 break;
1653 case kLine_Verb:
1654 tmp.lineTo(pts[1]);
1655 break;
1656 case kQuad_Verb:
1657 subdivide_quad_to(&tmp, pts);
1658 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001659 case kConic_Verb:
1660 SkASSERT(!"TODO: compute new weight");
1661 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1662 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 case kCubic_Verb:
1664 subdivide_cubic_to(&tmp, pts);
1665 break;
1666 case kClose_Verb:
1667 tmp.close();
1668 break;
1669 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001670 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 break;
1672 }
1673 }
1674
1675 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001676 SkPathRef::Editor ed(&dst->fPathRef);
1677 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001678 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001680 /*
1681 * If we're not in perspective, we can transform all of the points at
1682 * once.
1683 *
1684 * Here we also want to optimize bounds, by noting if the bounds are
1685 * already known, and if so, we just transform those as well and mark
1686 * them as "known", rather than force the transformed path to have to
1687 * recompute them.
1688 *
1689 * Special gotchas if the path is effectively empty (<= 1 point) or
1690 * if it is non-finite. In those cases bounds need to stay empty,
1691 * regardless of the matrix.
1692 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001694 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001695 if (fIsFinite) {
1696 matrix.mapRect(&dst->fBounds, fBounds);
1697 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1698 dst->fBounds.setEmpty();
1699 }
1700 } else {
1701 dst->fIsFinite = false;
1702 dst->fBounds.setEmpty();
1703 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001705 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001706 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 }
1708
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001709 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1710
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001713 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001714 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001716
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001717#ifdef SK_BUILD_FOR_ANDROID
1718 if (!matrix.isIdentity()) {
1719 GEN_ID_PTR_INC(dst);
1720 }
1721#endif
1722
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001723 if (kUnknown_Direction == fDirection) {
1724 dst->fDirection = kUnknown_Direction;
1725 } else {
1726 SkScalar det2x2 =
1727 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1728 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1729 if (det2x2 < 0) {
1730 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1731 } else if (det2x2 > 0) {
1732 dst->fDirection = fDirection;
1733 } else {
1734 dst->fDirection = kUnknown_Direction;
1735 }
1736 }
1737
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001738 // It's an oval only if it stays a rect.
1739 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001740
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 SkDEBUGCODE(dst->validate();)
1742 }
1743}
1744
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745///////////////////////////////////////////////////////////////////////////////
1746///////////////////////////////////////////////////////////////////////////////
1747
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001749 kEmptyContour_SegmentState, // The current contour is empty. We may be
1750 // starting processing or we may have just
1751 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001752 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1753 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1754 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755};
1756
1757SkPath::Iter::Iter() {
1758#ifdef SK_DEBUG
1759 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001760 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001762 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001763 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764#endif
1765 // need to init enough to make next() harmlessly return kDone_Verb
1766 fVerbs = NULL;
1767 fVerbStop = NULL;
1768 fNeedClose = false;
1769}
1770
1771SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1772 this->setPath(path, forceClose);
1773}
1774
1775void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001776 fPts = path.fPathRef->points();
1777 fVerbs = path.fPathRef->verbs();
1778 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001779 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001780 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001781 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 fForceClose = SkToU8(forceClose);
1783 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001784 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785}
1786
1787bool SkPath::Iter::isClosedContour() const {
1788 if (fVerbs == NULL || fVerbs == fVerbStop) {
1789 return false;
1790 }
1791 if (fForceClose) {
1792 return true;
1793 }
1794
1795 const uint8_t* verbs = fVerbs;
1796 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001798 if (kMove_Verb == *(verbs - 1)) {
1799 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 }
1801
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001802 while (verbs > stop) {
1803 // verbs points one beyond the current verb, decrement first.
1804 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 if (kMove_Verb == v) {
1806 break;
1807 }
1808 if (kClose_Verb == v) {
1809 return true;
1810 }
1811 }
1812 return false;
1813}
1814
1815SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001816 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001818 // A special case: if both points are NaN, SkPoint::operation== returns
1819 // false, but the iterator expects that they are treated as the same.
1820 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001821 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1822 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001823 return kClose_Verb;
1824 }
1825
reed@google.com9e25dbf2012-05-15 17:05:38 +00001826 pts[0] = fLastPt;
1827 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 fLastPt = fMoveTo;
1829 fCloseLine = true;
1830 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001831 } else {
1832 pts[0] = fMoveTo;
1833 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835}
1836
reed@google.com9e25dbf2012-05-15 17:05:38 +00001837const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 if (fSegmentState == kAfterMove_SegmentState) {
1839 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001841 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1844 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001845 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847}
1848
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849void SkPath::Iter::consumeDegenerateSegments() {
1850 // We need to step over anything that will not move the current draw point
1851 // forward before the next move is seen
1852 const uint8_t* lastMoveVerb = 0;
1853 const SkPoint* lastMovePt = 0;
1854 SkPoint lastPt = fLastPt;
1855 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001856 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 switch (verb) {
1858 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 // Keep a record of this most recent move
1860 lastMoveVerb = fVerbs;
1861 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001862 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 fPts++;
1865 break;
1866
1867 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001868 // A close when we are in a segment is always valid except when it
1869 // follows a move which follows a segment.
1870 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 return;
1872 }
1873 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 break;
1876
1877 case kLine_Verb:
1878 if (!IsLineDegenerate(lastPt, fPts[0])) {
1879 if (lastMoveVerb) {
1880 fVerbs = lastMoveVerb;
1881 fPts = lastMovePt;
1882 return;
1883 }
1884 return;
1885 }
1886 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001887 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 fPts++;
1889 break;
1890
reed@google.com277c3f82013-05-31 15:17:50 +00001891 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 case kQuad_Verb:
1893 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1894 if (lastMoveVerb) {
1895 fVerbs = lastMoveVerb;
1896 fPts = lastMovePt;
1897 return;
1898 }
1899 return;
1900 }
1901 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001902 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001904 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 break;
1906
1907 case kCubic_Verb:
1908 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1909 if (lastMoveVerb) {
1910 fVerbs = lastMoveVerb;
1911 fPts = lastMovePt;
1912 return;
1913 }
1914 return;
1915 }
1916 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001917 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fPts += 3;
1919 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001920
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001922 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 }
1924 }
1925}
1926
reed@google.com4a3b7142012-05-16 17:16:46 +00001927SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 // Close the curve if requested and if there is some curve to close
1932 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001933 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 return kLine_Verb;
1935 }
1936 fNeedClose = false;
1937 return kClose_Verb;
1938 }
1939 return kDone_Verb;
1940 }
1941
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 // fVerbs is one beyond the current verb, decrement first
1943 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001944 const SkPoint* SK_RESTRICT srcPts = fPts;
1945 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946
1947 switch (verb) {
1948 case kMove_Verb:
1949 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 verb = this->autoClose(pts);
1952 if (verb == kClose_Verb) {
1953 fNeedClose = false;
1954 }
1955 return (Verb)verb;
1956 }
1957 if (fVerbs == fVerbStop) { // might be a trailing moveto
1958 return kDone_Verb;
1959 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001960 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001961 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001963 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 fNeedClose = fForceClose;
1966 break;
1967 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 pts[0] = this->cons_moveTo();
1969 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fLastPt = srcPts[0];
1971 fCloseLine = false;
1972 srcPts += 1;
1973 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001974 case kConic_Verb:
1975 fConicWeights += 1;
1976 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001978 pts[0] = this->cons_moveTo();
1979 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 fLastPt = srcPts[1];
1981 srcPts += 2;
1982 break;
1983 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 pts[0] = this->cons_moveTo();
1985 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 fLastPt = srcPts[2];
1987 srcPts += 3;
1988 break;
1989 case kClose_Verb:
1990 verb = this->autoClose(pts);
1991 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001992 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993 } else {
1994 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001995 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 break;
1999 }
2000 fPts = srcPts;
2001 return (Verb)verb;
2002}
2003
2004///////////////////////////////////////////////////////////////////////////////
2005
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006SkPath::RawIter::RawIter() {
2007#ifdef SK_DEBUG
2008 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002009 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002010 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2011#endif
2012 // need to init enough to make next() harmlessly return kDone_Verb
2013 fVerbs = NULL;
2014 fVerbStop = NULL;
2015}
2016
2017SkPath::RawIter::RawIter(const SkPath& path) {
2018 this->setPath(path);
2019}
2020
2021void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 fPts = path.fPathRef->points();
2023 fVerbs = path.fPathRef->verbs();
2024 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002025 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002026 fMoveTo.fX = fMoveTo.fY = 0;
2027 fLastPt.fX = fLastPt.fY = 0;
2028}
2029
2030SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002031 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002032 if (fVerbs == fVerbStop) {
2033 return kDone_Verb;
2034 }
2035
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002036 // fVerbs points one beyond next verb so decrement first.
2037 unsigned verb = *(--fVerbs);
2038 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002039
2040 switch (verb) {
2041 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002042 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002043 fMoveTo = srcPts[0];
2044 fLastPt = fMoveTo;
2045 srcPts += 1;
2046 break;
2047 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002048 pts[0] = fLastPt;
2049 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002050 fLastPt = srcPts[0];
2051 srcPts += 1;
2052 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002053 case kConic_Verb:
2054 fConicWeights += 1;
2055 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002056 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002057 pts[0] = fLastPt;
2058 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002059 fLastPt = srcPts[1];
2060 srcPts += 2;
2061 break;
2062 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002063 pts[0] = fLastPt;
2064 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002065 fLastPt = srcPts[2];
2066 srcPts += 3;
2067 break;
2068 case kClose_Verb:
2069 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002070 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002071 break;
2072 }
2073 fPts = srcPts;
2074 return (Verb)verb;
2075}
2076
2077///////////////////////////////////////////////////////////////////////////////
2078
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002080 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081*/
2082
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002083uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084 SkDEBUGCODE(this->validate();)
2085
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002086 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002087 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002088 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002090 return SkAlign4(byteCount);
2091 }
2092
2093 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002094
2095 // Call getBounds() to ensure (as a side-effect) that fBounds
2096 // and fIsFinite are computed.
2097 const SkRect& bounds = this->getBounds();
2098 SkASSERT(!fBoundsIsDirty);
2099
2100 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2101 ((fIsOval & 1) << kIsOval_SerializationShift) |
2102 (fConvexity << kConvexity_SerializationShift) |
2103 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002104 (fSegmentMask << kSegmentMask_SerializationShift) |
2105 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002106
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002107 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002108
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002109 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002110
2111 buffer.write(&bounds, sizeof(bounds));
2112
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002113 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002114 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115}
2116
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002117uint32_t SkPath::readFromMemory(const void* storage) {
2118 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002119
reed@google.com98b11f12011-09-21 18:40:27 +00002120 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002121 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2122 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2123 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2124 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002125 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002126 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002127
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002128 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002129
2130 buffer.read(&fBounds, sizeof(fBounds));
2131 fBoundsIsDirty = false;
2132
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002133 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002134
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002135 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136
2137 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002138 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139}
2140
2141///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142
reed@google.com51bbe752013-01-17 22:07:50 +00002143#include "SkString.h"
2144
2145static void append_scalar(SkString* str, SkScalar value) {
2146 SkString tmp;
2147 tmp.printf("%g", value);
2148 if (tmp.contains('.')) {
2149 tmp.appendUnichar('f');
2150 }
2151 str->append(tmp);
2152}
2153
2154static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002155 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002156 str->append(label);
2157 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002158
reed@google.com51bbe752013-01-17 22:07:50 +00002159 const SkScalar* values = &pts[0].fX;
2160 count *= 2;
2161
2162 for (int i = 0; i < count; ++i) {
2163 append_scalar(str, values[i]);
2164 if (i < count - 1) {
2165 str->append(", ");
2166 }
2167 }
reed@google.com277c3f82013-05-31 15:17:50 +00002168 if (conicWeight >= 0) {
2169 str->append(", ");
2170 append_scalar(str, conicWeight);
2171 }
reed@google.com51bbe752013-01-17 22:07:50 +00002172 str->append(");\n");
2173}
2174
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175void SkPath::dump(bool forceClose, const char title[]) const {
2176 Iter iter(*this, forceClose);
2177 SkPoint pts[4];
2178 Verb verb;
2179
2180 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2181 title ? title : "");
2182
reed@google.com51bbe752013-01-17 22:07:50 +00002183 SkString builder;
2184
reed@google.com4a3b7142012-05-16 17:16:46 +00002185 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 switch (verb) {
2187 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002188 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189 break;
2190 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002191 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192 break;
2193 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002194 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002196 case kConic_Verb:
2197 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2198 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002200 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002201 break;
2202 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002203 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002204 break;
2205 default:
2206 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2207 verb = kDone_Verb; // stop the loop
2208 break;
2209 }
2210 }
reed@google.com51bbe752013-01-17 22:07:50 +00002211 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212}
2213
reed@android.come522ca52009-11-23 20:10:41 +00002214void SkPath::dump() const {
2215 this->dump(false);
2216}
2217
2218#ifdef SK_DEBUG
2219void SkPath::validate() const {
2220 SkASSERT(this != NULL);
2221 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002222
djsollen@google.com077348c2012-10-22 20:23:32 +00002223#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002224 if (!fBoundsIsDirty) {
2225 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002226
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002227 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002228 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002229
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002230 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002231 // if we're empty, fBounds may be empty but translated, so we can't
2232 // necessarily compare to bounds directly
2233 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2234 // be [2, 2, 2, 2]
2235 SkASSERT(bounds.isEmpty());
2236 SkASSERT(fBounds.isEmpty());
2237 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002238 if (bounds.isEmpty()) {
2239 SkASSERT(fBounds.isEmpty());
2240 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002241 if (!fBounds.isEmpty()) {
2242 SkASSERT(fBounds.contains(bounds));
2243 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002244 }
reed@android.come522ca52009-11-23 20:10:41 +00002245 }
2246 }
reed@google.com10296cc2011-09-21 12:29:05 +00002247
2248 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002249 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2250 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2251 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002252 case kLine_Verb:
2253 mask |= kLine_SegmentMask;
2254 break;
2255 case kQuad_Verb:
2256 mask |= kQuad_SegmentMask;
2257 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002258 case kConic_Verb:
2259 mask |= kConic_SegmentMask;
2260 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002261 case kCubic_Verb:
2262 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002263 case kMove_Verb: // these verbs aren't included in the segment mask.
2264 case kClose_Verb:
2265 break;
2266 case kDone_Verb:
2267 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2268 break;
2269 default:
2270 SkDEBUGFAIL("Unknown Verb");
2271 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002272 }
2273 }
2274 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002275#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002276}
djsollen@google.com077348c2012-10-22 20:23:32 +00002277#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002278
reed@google.com04863fa2011-05-15 04:08:24 +00002279///////////////////////////////////////////////////////////////////////////////
2280
reed@google.com0b7b9822011-05-16 12:29:27 +00002281static int sign(SkScalar x) { return x < 0; }
2282#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002283
reed@google.com04863fa2011-05-15 04:08:24 +00002284static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002285 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002286}
2287
2288// only valid for a single contour
2289struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002290 Convexicator()
2291 : fPtCount(0)
2292 , fConvexity(SkPath::kConvex_Convexity)
2293 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002294 fSign = 0;
2295 // warnings
2296 fCurrPt.set(0, 0);
2297 fVec0.set(0, 0);
2298 fVec1.set(0, 0);
2299 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002300
2301 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002302 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002303 }
2304
2305 SkPath::Convexity getConvexity() const { return fConvexity; }
2306
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002307 /** The direction returned is only valid if the path is determined convex */
2308 SkPath::Direction getDirection() const { return fDirection; }
2309
reed@google.com04863fa2011-05-15 04:08:24 +00002310 void addPt(const SkPoint& pt) {
2311 if (SkPath::kConcave_Convexity == fConvexity) {
2312 return;
2313 }
2314
2315 if (0 == fPtCount) {
2316 fCurrPt = pt;
2317 ++fPtCount;
2318 } else {
2319 SkVector vec = pt - fCurrPt;
2320 if (vec.fX || vec.fY) {
2321 fCurrPt = pt;
2322 if (++fPtCount == 2) {
2323 fFirstVec = fVec1 = vec;
2324 } else {
2325 SkASSERT(fPtCount > 2);
2326 this->addVec(vec);
2327 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002328
reed@google.com85b6e392011-05-15 20:25:17 +00002329 int sx = sign(vec.fX);
2330 int sy = sign(vec.fY);
2331 fDx += (sx != fSx);
2332 fDy += (sy != fSy);
2333 fSx = sx;
2334 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002335
reed@google.com85b6e392011-05-15 20:25:17 +00002336 if (fDx > 3 || fDy > 3) {
2337 fConvexity = SkPath::kConcave_Convexity;
2338 }
reed@google.com04863fa2011-05-15 04:08:24 +00002339 }
2340 }
2341 }
2342
2343 void close() {
2344 if (fPtCount > 2) {
2345 this->addVec(fFirstVec);
2346 }
2347 }
2348
2349private:
2350 void addVec(const SkVector& vec) {
2351 SkASSERT(vec.fX || vec.fY);
2352 fVec0 = fVec1;
2353 fVec1 = vec;
2354 int sign = CrossProductSign(fVec0, fVec1);
2355 if (0 == fSign) {
2356 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357 if (1 == sign) {
2358 fDirection = SkPath::kCW_Direction;
2359 } else if (-1 == sign) {
2360 fDirection = SkPath::kCCW_Direction;
2361 }
reed@google.com04863fa2011-05-15 04:08:24 +00002362 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002363 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002364 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002365 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002366 }
2367 }
2368 }
2369
2370 SkPoint fCurrPt;
2371 SkVector fVec0, fVec1, fFirstVec;
2372 int fPtCount; // non-degenerate points
2373 int fSign;
2374 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002375 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002376 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002377};
2378
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002379SkPath::Convexity SkPath::internalGetConvexity() const {
2380 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002381 SkPoint pts[4];
2382 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002383 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002384
2385 int contourCount = 0;
2386 int count;
2387 Convexicator state;
2388
2389 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2390 switch (verb) {
2391 case kMove_Verb:
2392 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002393 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002394 return kConcave_Convexity;
2395 }
2396 pts[1] = pts[0];
2397 count = 1;
2398 break;
2399 case kLine_Verb: count = 1; break;
2400 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002401 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 case kCubic_Verb: count = 3; break;
2403 case kClose_Verb:
2404 state.close();
2405 count = 0;
2406 break;
2407 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002408 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 return kConcave_Convexity;
2411 }
2412
2413 for (int i = 1; i <= count; i++) {
2414 state.addPt(pts[i]);
2415 }
2416 // early exit
2417 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002418 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 return kConcave_Convexity;
2420 }
2421 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 fConvexity = state.getConvexity();
2423 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2424 fDirection = state.getDirection();
2425 }
2426 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002427}
reed@google.com69a99432012-01-10 18:00:10 +00002428
2429///////////////////////////////////////////////////////////////////////////////
2430
2431class ContourIter {
2432public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002433 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002434
2435 bool done() const { return fDone; }
2436 // if !done() then these may be called
2437 int count() const { return fCurrPtCount; }
2438 const SkPoint* pts() const { return fCurrPt; }
2439 void next();
2440
2441private:
2442 int fCurrPtCount;
2443 const SkPoint* fCurrPt;
2444 const uint8_t* fCurrVerb;
2445 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002446 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002447 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002448 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002449};
2450
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002451ContourIter::ContourIter(const SkPathRef& pathRef) {
2452 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002453 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002454 fCurrPt = pathRef.points();
2455 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002456 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002457 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002458 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002459 this->next();
2460}
2461
2462void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002463 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002464 fDone = true;
2465 }
2466 if (fDone) {
2467 return;
2468 }
2469
2470 // skip pts of prev contour
2471 fCurrPt += fCurrPtCount;
2472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002473 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002474 int ptCount = 1; // moveTo
2475 const uint8_t* verbs = fCurrVerb;
2476
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002477 for (--verbs; verbs > fStopVerbs; --verbs) {
2478 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002479 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002480 goto CONTOUR_END;
2481 case SkPath::kLine_Verb:
2482 ptCount += 1;
2483 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002484 case SkPath::kConic_Verb:
2485 fCurrConicWeight += 1;
2486 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002487 case SkPath::kQuad_Verb:
2488 ptCount += 2;
2489 break;
2490 case SkPath::kCubic_Verb:
2491 ptCount += 3;
2492 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002493 case SkPath::kClose_Verb:
2494 break;
2495 default:
2496 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002497 break;
2498 }
2499 }
2500CONTOUR_END:
2501 fCurrPtCount = ptCount;
2502 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002503 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002504}
2505
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002506// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002507static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002508 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2509 // We may get 0 when the above subtracts underflow. We expect this to be
2510 // very rare and lazily promote to double.
2511 if (0 == cross) {
2512 double p0x = SkScalarToDouble(p0.fX);
2513 double p0y = SkScalarToDouble(p0.fY);
2514
2515 double p1x = SkScalarToDouble(p1.fX);
2516 double p1y = SkScalarToDouble(p1.fY);
2517
2518 double p2x = SkScalarToDouble(p2.fX);
2519 double p2y = SkScalarToDouble(p2.fY);
2520
2521 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2522 (p1y - p0y) * (p2x - p0x));
2523
2524 }
2525 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002526}
2527
reed@google.comc1ea60a2012-01-31 15:15:36 +00002528// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002529static int find_max_y(const SkPoint pts[], int count) {
2530 SkASSERT(count > 0);
2531 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002532 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002533 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002534 SkScalar y = pts[i].fY;
2535 if (y > max) {
2536 max = y;
2537 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002538 }
2539 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002540 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002541}
2542
reed@google.comcabaf1d2012-01-11 21:03:05 +00002543static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2544 int i = index;
2545 for (;;) {
2546 i = (i + inc) % n;
2547 if (i == index) { // we wrapped around, so abort
2548 break;
2549 }
2550 if (pts[index] != pts[i]) { // found a different point, success!
2551 break;
2552 }
2553 }
2554 return i;
2555}
2556
reed@google.comc1ea60a2012-01-31 15:15:36 +00002557/**
2558 * Starting at index, and moving forward (incrementing), find the xmin and
2559 * xmax of the contiguous points that have the same Y.
2560 */
2561static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2562 int* maxIndexPtr) {
2563 const SkScalar y = pts[index].fY;
2564 SkScalar min = pts[index].fX;
2565 SkScalar max = min;
2566 int minIndex = index;
2567 int maxIndex = index;
2568 for (int i = index + 1; i < n; ++i) {
2569 if (pts[i].fY != y) {
2570 break;
2571 }
2572 SkScalar x = pts[i].fX;
2573 if (x < min) {
2574 min = x;
2575 minIndex = i;
2576 } else if (x > max) {
2577 max = x;
2578 maxIndex = i;
2579 }
2580 }
2581 *maxIndexPtr = maxIndex;
2582 return minIndex;
2583}
2584
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002585static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002586 if (dir) {
2587 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2588 }
reed@google.comac8543f2012-01-30 20:51:25 +00002589}
2590
reed@google.comc1ea60a2012-01-31 15:15:36 +00002591#if 0
2592#include "SkString.h"
2593#include "../utils/SkParsePath.h"
2594static void dumpPath(const SkPath& path) {
2595 SkString str;
2596 SkParsePath::ToSVGString(path, &str);
2597 SkDebugf("%s\n", str.c_str());
2598}
2599#endif
2600
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002601namespace {
2602// for use with convex_dir_test
2603double mul(double a, double b) { return a * b; }
2604SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2605double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2606SkScalar toScalar(SkScalar a) { return a; }
2607
2608// determines the winding direction of a convex polygon with the precision
2609// of T. CAST_SCALAR casts an SkScalar to T.
2610template <typename T, T (CAST_SCALAR)(SkScalar)>
2611bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2612 // we find the first three points that form a non-degenerate
2613 // triangle. If there are no such points then the path is
2614 // degenerate. The first is always point 0. Now we find the second
2615 // point.
2616 int i = 0;
2617 enum { kX = 0, kY = 1 };
2618 T v0[2];
2619 while (1) {
2620 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2621 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2622 if (v0[kX] || v0[kY]) {
2623 break;
2624 }
2625 if (++i == n - 1) {
2626 return false;
2627 }
2628 }
2629 // now find a third point that is not colinear with the first two
2630 // points and check the orientation of the triangle (which will be
2631 // the same as the orientation of the path).
2632 for (++i; i < n; ++i) {
2633 T v1[2];
2634 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2635 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2636 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2637 if (0 != cross) {
2638 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2639 return true;
2640 }
2641 }
2642 return false;
2643}
2644}
2645
reed@google.comac8543f2012-01-30 20:51:25 +00002646/*
2647 * We loop through all contours, and keep the computed cross-product of the
2648 * contour that contained the global y-max. If we just look at the first
2649 * contour, we may find one that is wound the opposite way (correctly) since
2650 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2651 * that is outer most (or at least has the global y-max) before we can consider
2652 * its cross product.
2653 */
reed@google.com69a99432012-01-10 18:00:10 +00002654bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002655// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002656 // don't want to pay the cost for computing this if it
2657 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002658
2659 if (kUnknown_Direction != fDirection) {
2660 *dir = static_cast<Direction>(fDirection);
2661 return true;
2662 }
reed@google.com69a99432012-01-10 18:00:10 +00002663 const Convexity conv = this->getConvexityOrUnknown();
2664
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002665 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002666
reed@google.comac8543f2012-01-30 20:51:25 +00002667 // initialize with our logical y-min
2668 SkScalar ymax = this->getBounds().fTop;
2669 SkScalar ymaxCross = 0;
2670
reed@google.com69a99432012-01-10 18:00:10 +00002671 for (; !iter.done(); iter.next()) {
2672 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002673 if (n < 3) {
2674 continue;
2675 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002676
reed@google.comcabaf1d2012-01-11 21:03:05 +00002677 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002678 SkScalar cross = 0;
2679 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002680 // We try first at scalar precision, and then again at double
2681 // precision. This is because the vectors computed between distant
2682 // points may lose too much precision.
2683 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002684 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002685 return true;
2686 }
2687 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002688 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002689 return true;
2690 } else {
2691 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002692 }
2693 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002694 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002695 if (pts[index].fY < ymax) {
2696 continue;
2697 }
2698
reed@google.comc1ea60a2012-01-31 15:15:36 +00002699 // If there is more than 1 distinct point at the y-max, we take the
2700 // x-min and x-max of them and just subtract to compute the dir.
2701 if (pts[(index + 1) % n].fY == pts[index].fY) {
2702 int maxIndex;
2703 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002704 if (minIndex == maxIndex) {
2705 goto TRY_CROSSPROD;
2706 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002707 SkASSERT(pts[minIndex].fY == pts[index].fY);
2708 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2709 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2710 // we just subtract the indices, and let that auto-convert to
2711 // SkScalar, since we just want - or + to signal the direction.
2712 cross = minIndex - maxIndex;
2713 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002714 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002715 // Find a next and prev index to use for the cross-product test,
2716 // but we try to find pts that form non-zero vectors from pts[index]
2717 //
2718 // Its possible that we can't find two non-degenerate vectors, so
2719 // we have to guard our search (e.g. all the pts could be in the
2720 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002721
reed@google.comc1ea60a2012-01-31 15:15:36 +00002722 // we pass n - 1 instead of -1 so we don't foul up % operator by
2723 // passing it a negative LH argument.
2724 int prev = find_diff_pt(pts, index, n, n - 1);
2725 if (prev == index) {
2726 // completely degenerate, skip to next contour
2727 continue;
2728 }
2729 int next = find_diff_pt(pts, index, n, 1);
2730 SkASSERT(next != index);
2731 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002732 // 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 +00002733 // x-direction. We really should continue to walk away from the degeneracy until
2734 // there is a divergence.
2735 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002736 // construct the subtract so we get the correct Direction below
2737 cross = pts[index].fX - pts[next].fX;
2738 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002739 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002740
reed@google.comac8543f2012-01-30 20:51:25 +00002741 if (cross) {
2742 // record our best guess so far
2743 ymax = pts[index].fY;
2744 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002745 }
reed@google.com69a99432012-01-10 18:00:10 +00002746 }
2747 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002748 if (ymaxCross) {
2749 crossToDir(ymaxCross, dir);
2750 fDirection = *dir;
2751 return true;
2752 } else {
2753 return false;
2754 }
reed@google.comac8543f2012-01-30 20:51:25 +00002755}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756
2757///////////////////////////////////////////////////////////////////////////////
2758
2759static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2760 SkScalar D, SkScalar t) {
2761 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2762}
2763
2764static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2765 SkScalar t) {
2766 SkScalar A = c3 + 3*(c1 - c2) - c0;
2767 SkScalar B = 3*(c2 - c1 - c1 + c0);
2768 SkScalar C = 3*(c1 - c0);
2769 SkScalar D = c0;
2770 return eval_cubic_coeff(A, B, C, D, t);
2771}
2772
2773/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2774 t value such that cubic(t) = target
2775 */
2776static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2777 SkScalar target, SkScalar* t) {
2778 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2779 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002780
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 SkScalar D = c0 - target;
2782 SkScalar A = c3 + 3*(c1 - c2) - c0;
2783 SkScalar B = 3*(c2 - c1 - c1 + c0);
2784 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2787 SkScalar minT = 0;
2788 SkScalar maxT = SK_Scalar1;
2789 SkScalar mid;
2790 int i;
2791 for (i = 0; i < 16; i++) {
2792 mid = SkScalarAve(minT, maxT);
2793 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2794 if (delta < 0) {
2795 minT = mid;
2796 delta = -delta;
2797 } else {
2798 maxT = mid;
2799 }
2800 if (delta < TOLERANCE) {
2801 break;
2802 }
2803 }
2804 *t = mid;
2805 return true;
2806}
2807
2808template <size_t N> static void find_minmax(const SkPoint pts[],
2809 SkScalar* minPtr, SkScalar* maxPtr) {
2810 SkScalar min, max;
2811 min = max = pts[0].fX;
2812 for (size_t i = 1; i < N; ++i) {
2813 min = SkMinScalar(min, pts[i].fX);
2814 max = SkMaxScalar(max, pts[i].fX);
2815 }
2816 *minPtr = min;
2817 *maxPtr = max;
2818}
2819
2820static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2821 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002822
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002823 int dir = 1;
2824 if (pts[0].fY > pts[3].fY) {
2825 storage[0] = pts[3];
2826 storage[1] = pts[2];
2827 storage[2] = pts[1];
2828 storage[3] = pts[0];
2829 pts = storage;
2830 dir = -1;
2831 }
2832 if (y < pts[0].fY || y >= pts[3].fY) {
2833 return 0;
2834 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 // quickreject or quickaccept
2837 SkScalar min, max;
2838 find_minmax<4>(pts, &min, &max);
2839 if (x < min) {
2840 return 0;
2841 }
2842 if (x > max) {
2843 return dir;
2844 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002845
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 // compute the actual x(t) value
2847 SkScalar t, xt;
2848 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2849 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2850 } else {
2851 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2852 xt = y < mid ? pts[0].fX : pts[3].fX;
2853 }
2854 return xt < x ? dir : 0;
2855}
2856
2857static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2858 SkPoint dst[10];
2859 int n = SkChopCubicAtYExtrema(pts, dst);
2860 int w = 0;
2861 for (int i = 0; i <= n; ++i) {
2862 w += winding_mono_cubic(&dst[i * 3], x, y);
2863 }
2864 return w;
2865}
2866
2867static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2868 SkScalar y0 = pts[0].fY;
2869 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002870
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002871 int dir = 1;
2872 if (y0 > y2) {
2873 SkTSwap(y0, y2);
2874 dir = -1;
2875 }
2876 if (y < y0 || y >= y2) {
2877 return 0;
2878 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 // bounds check on X (not required. is it faster?)
2881#if 0
2882 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2883 return 0;
2884 }
2885#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002886
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002887 SkScalar roots[2];
2888 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2889 2 * (pts[1].fY - pts[0].fY),
2890 pts[0].fY - y,
2891 roots);
2892 SkASSERT(n <= 1);
2893 SkScalar xt;
2894 if (0 == n) {
2895 SkScalar mid = SkScalarAve(y0, y2);
2896 // Need [0] and [2] if dir == 1
2897 // and [2] and [0] if dir == -1
2898 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2899 } else {
2900 SkScalar t = roots[0];
2901 SkScalar C = pts[0].fX;
2902 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2903 SkScalar B = 2 * (pts[1].fX - C);
2904 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2905 }
2906 return xt < x ? dir : 0;
2907}
2908
2909static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2910 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2911 if (y0 == y1) {
2912 return true;
2913 }
2914 if (y0 < y1) {
2915 return y1 <= y2;
2916 } else {
2917 return y1 >= y2;
2918 }
2919}
2920
2921static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2922 SkPoint dst[5];
2923 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002924
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002925 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2926 n = SkChopQuadAtYExtrema(pts, dst);
2927 pts = dst;
2928 }
2929 int w = winding_mono_quad(pts, x, y);
2930 if (n > 0) {
2931 w += winding_mono_quad(&pts[2], x, y);
2932 }
2933 return w;
2934}
2935
2936static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2937 SkScalar x0 = pts[0].fX;
2938 SkScalar y0 = pts[0].fY;
2939 SkScalar x1 = pts[1].fX;
2940 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002941
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002942 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002943
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002944 int dir = 1;
2945 if (y0 > y1) {
2946 SkTSwap(y0, y1);
2947 dir = -1;
2948 }
2949 if (y < y0 || y >= y1) {
2950 return 0;
2951 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002952
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002953 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2954 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002955
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002956 if (SkScalarSignAsInt(cross) == dir) {
2957 dir = 0;
2958 }
2959 return dir;
2960}
2961
2962bool SkPath::contains(SkScalar x, SkScalar y) const {
2963 bool isInverse = this->isInverseFillType();
2964 if (this->isEmpty()) {
2965 return isInverse;
2966 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002967
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 const SkRect& bounds = this->getBounds();
2969 if (!bounds.contains(x, y)) {
2970 return isInverse;
2971 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002972
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002973 SkPath::Iter iter(*this, true);
2974 bool done = false;
2975 int w = 0;
2976 do {
2977 SkPoint pts[4];
2978 switch (iter.next(pts, false)) {
2979 case SkPath::kMove_Verb:
2980 case SkPath::kClose_Verb:
2981 break;
2982 case SkPath::kLine_Verb:
2983 w += winding_line(pts, x, y);
2984 break;
2985 case SkPath::kQuad_Verb:
2986 w += winding_quad(pts, x, y);
2987 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002988 case SkPath::kConic_Verb:
2989 SkASSERT(0);
2990 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 case SkPath::kCubic_Verb:
2992 w += winding_cubic(pts, x, y);
2993 break;
2994 case SkPath::kDone_Verb:
2995 done = true;
2996 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002997 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002999
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 switch (this->getFillType()) {
3001 case SkPath::kEvenOdd_FillType:
3002 case SkPath::kInverseEvenOdd_FillType:
3003 w &= 1;
3004 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003005 default:
3006 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 }
3008 return SkToBool(w);
3009}