blob: e997b80e4f491803fe623b95152f3b3e3cb81e19 [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.comae09f2d2012-10-03 19:57:01 +000018////////////////////////////////////////////////////////////////////////////
19
20#if SK_DEBUG_PATH_REF
21
22SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
23
24SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
25: fPathRef(pr)
26, fOwner(owner) {
27 pr->addOwner(owner);
28}
29
30SkPath::PathRefDebugRef::~PathRefDebugRef() {
31 fPathRef->removeOwner(fOwner);
32}
33
34void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
35 bool diff = (ref != fPathRef.get());
36 if (diff && NULL != fPathRef.get()) {
37 fPathRef.get()->removeOwner(fOwner);
38 }
39 fPathRef.reset(ref);
40 if (diff && NULL != fPathRef.get()) {
41 fPathRef.get()->addOwner(fOwner);
42 }
43}
44
45void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
46 if (other->fPathRef.get() != fPathRef.get()) {
47 other->fPathRef->removeOwner(other->fOwner);
48 other->fPathRef->addOwner(fOwner);
49
50 fPathRef->removeOwner(fOwner);
51 fPathRef->addOwner(other->fOwner);
52 }
53
54 fPathRef.swap(&other->fPathRef);
55}
56
57SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
58
59SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
60 return fPathRef.operator->();
61}
62
63SkPath::PathRefDebugRef::operator SkPathRef*() {
64 return fPathRef.operator SkPathRef *();
65}
66
67#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000068
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000069////////////////////////////////////////////////////////////////////////////
70
71
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000072SK_DEFINE_INST_COUNT(SkPath);
73
reed@google.com744faba2012-05-29 19:54:52 +000074// This value is just made-up for now. When count is 4, calling memset was much
75// slower than just writing the loop. This seems odd, and hopefully in the
76// future this we appear to have been a fluke...
77#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
78
reed@android.com8a1c16f2008-12-17 15:59:43 +000079////////////////////////////////////////////////////////////////////////////
80
reed@google.com3563c9e2011-11-14 19:34:57 +000081/**
82 * Path.bounds is defined to be the bounds of all the control points.
83 * If we called bounds.join(r) we would skip r if r was empty, which breaks
84 * our promise. Hence we have a custom joiner that doesn't look at emptiness
85 */
86static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
87 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
88 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
89 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
90 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
91}
92
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000093static bool is_degenerate(const SkPath& path) {
94 SkPath::Iter iter(path, false);
95 SkPoint pts[4];
96 return SkPath::kDone_Verb == iter.next(pts);
97}
98
bsalomon@google.com6aa29652012-04-18 13:29:52 +000099class SkAutoDisableOvalCheck {
100public:
101 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
102 fSaved = fPath->fIsOval;
103 }
104
105 ~SkAutoDisableOvalCheck() {
106 fPath->fIsOval = fSaved;
107 }
108
109private:
110 SkPath* fPath;
111 bool fSaved;
112};
113
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000114class SkAutoDisableDirectionCheck {
115public:
116 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
117 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
118 }
119
120 ~SkAutoDisableDirectionCheck() {
121 fPath->fDirection = fSaved;
122 }
123
124private:
125 SkPath* fPath;
126 SkPath::Direction fSaved;
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129/* This guy's constructor/destructor bracket a path editing operation. It is
130 used when we know the bounds of the amount we are going to add to the path
131 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 It captures some state about the path up front (i.e. if it already has a
134 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000135 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000136
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000137 It also notes if the path was originally degenerate, and if so, sets
138 isConvex to true. Thus it can only be used if the contour being added is
139 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 */
141class SkAutoPathBoundsUpdate {
142public:
143 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
144 this->init(path);
145 }
146
147 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
148 SkScalar right, SkScalar bottom) {
149 fRect.set(left, top, right, bottom);
150 this->init(path);
151 }
reed@google.comabf15c12011-01-18 20:35:51 +0000152
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000154 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000156 fPath->fBounds = fRect;
157 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000158 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000160 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000161 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000162 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 }
164 }
reed@google.comabf15c12011-01-18 20:35:51 +0000165
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000167 SkPath* fPath;
168 SkRect fRect;
169 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000170 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000171 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000174 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000176 // Mark the path's bounds as dirty if (1) they are, or (2) the path
177 // is non-finite, and therefore its bounds are not meaningful
178 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000179 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000181 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000182 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 }
184};
185
reed@google.com0bb18bb2012-07-26 15:20:36 +0000186// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000187static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
188 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000189 if (count <= 1) { // we ignore just 1 point (moveto)
190 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000191 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000193 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 }
195}
196
197////////////////////////////////////////////////////////////////////////////
198
199/*
200 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000201 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
203
204 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000205 1. If we encounter degenerate segments, remove them
206 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
207 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
208 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209*/
210
211////////////////////////////////////////////////////////////////////////////
212
reed@google.comd335d1d2012-01-12 18:17:11 +0000213// flag to require a moveTo if we begin with something else, like lineTo etc.
214#define INITIAL_LASTMOVETOINDEX_VALUE ~0
215
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000216SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000217#if SK_DEBUG_PATH_REF
218 : fPathRef(SkPathRef::CreateEmpty(), this)
219#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000220 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000221#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000222#ifdef SK_BUILD_FOR_ANDROID
223 , fGenerationID(0)
224#endif
225{
226 this->resetFields();
227}
228
229void SkPath::resetFields() {
230 //fPathRef is assumed to have been emptied by the caller.
231 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
232 fFillType = kWinding_FillType;
233 fSegmentMask = 0;
234 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000235 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000236 fDirection = kUnknown_Direction;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000237 fIsFinite = false;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000238 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000239#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000240 GEN_ID_INC;
djsollen@google.come63793a2012-03-21 15:39:03 +0000241 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000242#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000243}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
bungeman@google.coma5809a32013-06-21 15:13:34 +0000245SkPath::SkPath(const SkPath& that)
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000246#if SK_DEBUG_PATH_REF
247 : fPathRef(this)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000248#else
249 : fPathRef(SkRef(that.fPathRef.get()))
250#endif
251#ifdef SK_BUILD_FOR_ANDROID
252 , fGenerationID(0)
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000253#endif
254{
bungeman@google.coma5809a32013-06-21 15:13:34 +0000255 this->copyFields(that);
256 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257}
258
259SkPath::~SkPath() {
260 SkDEBUGCODE(this->validate();)
261}
262
bungeman@google.coma5809a32013-06-21 15:13:34 +0000263SkPath& SkPath::operator=(const SkPath& that) {
264 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265
bungeman@google.coma5809a32013-06-21 15:13:34 +0000266 if (this != &that) {
267 fPathRef.reset(SkRef(that.fPathRef.get()));
268 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 }
270 SkDEBUGCODE(this->validate();)
271 return *this;
272}
273
bungeman@google.coma5809a32013-06-21 15:13:34 +0000274void SkPath::copyFields(const SkPath& that) {
275 //fPathRef is assumed to have been set by the caller.
276 fBounds = that.fBounds;
277 fLastMoveToIndex = that.fLastMoveToIndex;
278 fFillType = that.fFillType;
279 fSegmentMask = that.fSegmentMask;
280 fBoundsIsDirty = that.fBoundsIsDirty;
281 fConvexity = that.fConvexity;
282 fDirection = that.fDirection;
283 fIsFinite = that.fIsFinite;
284 fIsOval = that.fIsOval;
285#ifdef SK_BUILD_FOR_ANDROID
286 GEN_ID_INC;
287 fSourcePath = NULL;
288#endif
289}
290
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000291SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000292 // note: don't need to look at isConvex or bounds, since just comparing the
293 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000294
295 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
296 // since it is only a cache of info in the fVerbs, but its a fast way to
297 // notice a difference
298
reed@android.com3abec1d2009-03-02 05:36:20 +0000299 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000300 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000301 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000302}
303
bungeman@google.coma5809a32013-06-21 15:13:34 +0000304void SkPath::swap(SkPath& that) {
305 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306
bungeman@google.coma5809a32013-06-21 15:13:34 +0000307 if (this != &that) {
308 fPathRef.swap(&that.fPathRef);
309 SkTSwap<SkRect>(fBounds, that.fBounds);
310 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
311 SkTSwap<uint8_t>(fFillType, that.fFillType);
312 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
313 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
314 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
315 SkTSwap<uint8_t>(fDirection, that.fDirection);
316 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
317 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000318 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000319 GEN_ID_PTR_INC(&that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 }
321}
322
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000323static inline bool check_edge_against_rect(const SkPoint& p0,
324 const SkPoint& p1,
325 const SkRect& rect,
326 SkPath::Direction dir) {
327 const SkPoint* edgeBegin;
328 SkVector v;
329 if (SkPath::kCW_Direction == dir) {
330 v = p1 - p0;
331 edgeBegin = &p0;
332 } else {
333 v = p0 - p1;
334 edgeBegin = &p1;
335 }
336 if (v.fX || v.fY) {
337 // check the cross product of v with the vec from edgeBegin to each rect corner
338 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
339 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
340 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
341 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
342 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
343 return false;
344 }
345 }
346 return true;
347}
348
349bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
350 // This only handles non-degenerate convex paths currently.
351 if (kConvex_Convexity != this->getConvexity()) {
352 return false;
353 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000354
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000355 Direction direction;
356 if (!this->cheapComputeDirection(&direction)) {
357 return false;
358 }
359
360 SkPoint firstPt;
361 SkPoint prevPt;
362 RawIter iter(*this);
363 SkPath::Verb verb;
364 SkPoint pts[4];
365 SkDEBUGCODE(int moveCnt = 0;)
366
367 while ((verb = iter.next(pts)) != kDone_Verb) {
368 int nextPt = -1;
369 switch (verb) {
370 case kMove_Verb:
371 SkASSERT(!moveCnt);
372 SkDEBUGCODE(++moveCnt);
373 firstPt = prevPt = pts[0];
374 break;
375 case kLine_Verb:
376 nextPt = 1;
377 SkASSERT(moveCnt);
378 break;
379 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000380 case kConic_Verb:
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000381 SkASSERT(moveCnt);
382 nextPt = 2;
383 break;
384 case kCubic_Verb:
385 SkASSERT(moveCnt);
386 nextPt = 3;
387 break;
388 case kClose_Verb:
389 break;
390 default:
391 SkDEBUGFAIL("unknown verb");
392 }
393 if (-1 != nextPt) {
394 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
395 return false;
396 }
397 prevPt = pts[nextPt];
398 }
399 }
400
401 return check_edge_against_rect(prevPt, firstPt, rect, direction);
402}
403
djsollen@google.com56c69772011-11-08 19:00:26 +0000404#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000405uint32_t SkPath::getGenerationID() const {
406 return fGenerationID;
407}
djsollen@google.come63793a2012-03-21 15:39:03 +0000408
409const SkPath* SkPath::getSourcePath() const {
410 return fSourcePath;
411}
412
413void SkPath::setSourcePath(const SkPath* path) {
414 fSourcePath = path;
415}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000416#endif
417
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418void SkPath::reset() {
419 SkDEBUGCODE(this->validate();)
420
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000421 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000422 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423}
424
425void SkPath::rewind() {
426 SkDEBUGCODE(this->validate();)
427
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000428 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000429 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430}
431
432bool SkPath::isEmpty() const {
433 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000434 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435}
436
reed@google.com7e6c4d12012-05-10 14:05:43 +0000437bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000438 int verbCount = fPathRef->countVerbs();
439 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000440
reed@google.com7e6c4d12012-05-10 14:05:43 +0000441 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000442 if (kMove_Verb == fPathRef->atVerb(0) &&
443 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000444 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000445 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000446 line[0] = pts[0];
447 line[1] = pts[1];
448 }
449 return true;
450 }
451 }
452 return false;
453}
454
caryclark@google.comf1316942011-07-26 19:54:45 +0000455/*
456 Determines if path is a rect by keeping track of changes in direction
457 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000458
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 The direction is computed such that:
460 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000461 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000463 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000464
caryclark@google.comf1316942011-07-26 19:54:45 +0000465A rectangle cycles up/right/down/left or up/left/down/right.
466
467The test fails if:
468 The path is closed, and followed by a line.
469 A second move creates a new endpoint.
470 A diagonal line is parsed.
471 There's more than four changes of direction.
472 There's a discontinuity on the line (e.g., a move in the middle)
473 The line reverses direction.
474 The rectangle doesn't complete a cycle.
475 The path contains a quadratic or cubic.
476 The path contains fewer than four points.
477 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000478
caryclark@google.comf1316942011-07-26 19:54:45 +0000479It's OK if the path has:
480 Several colinear line segments composing a rectangle side.
481 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000482
caryclark@google.comf1316942011-07-26 19:54:45 +0000483The direction takes advantage of the corners found since opposite sides
484must travel in opposite directions.
485
486FIXME: Allow colinear quads and cubics to be treated like lines.
487FIXME: If the API passes fill-only, return true if the filled stroke
488 is a rectangle, though the caller failed to close the path.
489 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000490bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
491 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 int corners = 0;
493 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000494 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000496 first.set(0, 0);
497 last.set(0, 0);
498 int firstDirection = 0;
499 int lastDirection = 0;
500 int nextDirection = 0;
501 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000503 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
505 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 savePts = pts;
508 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 autoClose = true;
510 case kLine_Verb: {
511 SkScalar left = last.fX;
512 SkScalar top = last.fY;
513 SkScalar right = pts->fX;
514 SkScalar bottom = pts->fY;
515 ++pts;
516 if (left != right && top != bottom) {
517 return false; // diagonal
518 }
519 if (left == right && top == bottom) {
520 break; // single point on side OK
521 }
522 nextDirection = (left != right) << 0 |
523 (left < right || top < bottom) << 1;
524 if (0 == corners) {
525 firstDirection = nextDirection;
526 first = last;
527 last = pts[-1];
528 corners = 1;
529 closedOrMoved = false;
530 break;
531 }
532 if (closedOrMoved) {
533 return false; // closed followed by a line
534 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000535 if (autoClose && nextDirection == firstDirection) {
536 break; // colinear with first
537 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000538 closedOrMoved = autoClose;
539 if (lastDirection != nextDirection) {
540 if (++corners > 4) {
541 return false; // too many direction changes
542 }
543 }
544 last = pts[-1];
545 if (lastDirection == nextDirection) {
546 break; // colinear segment
547 }
548 // Possible values for corners are 2, 3, and 4.
549 // When corners == 3, nextDirection opposes firstDirection.
550 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000551 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000552 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
553 if ((directionCycle ^ turn) != nextDirection) {
554 return false; // direction didn't follow cycle
555 }
556 break;
557 }
558 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000559 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 case kCubic_Verb:
561 return false; // quadratic, cubic not allowed
562 case kMove_Verb:
563 last = *pts++;
564 closedOrMoved = true;
565 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000566 default:
567 SkASSERT(!"unexpected verb");
568 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000569 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000571 lastDirection = nextDirection;
572 }
573 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000574 bool result = 4 == corners && (first == last || autoClose);
575 if (savePts) {
576 *ptsPtr = savePts;
577 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000578 if (result && isClosed) {
579 *isClosed = autoClose;
580 }
581 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000582 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000583 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000584 return result;
585}
586
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000587bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 SkDEBUGCODE(this->validate();)
589 int currVerb = 0;
590 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000591 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
592 if (result && rect) {
593 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000594 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000595 return result;
596}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000597
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000598bool SkPath::isRect(bool* isClosed, Direction* direction) const {
599 SkDEBUGCODE(this->validate();)
600 int currVerb = 0;
601 const SkPoint* pts = fPathRef->points();
602 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000603}
604
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000605bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000606 SkDEBUGCODE(this->validate();)
607 int currVerb = 0;
608 const SkPoint* pts = fPathRef->points();
609 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000610 Direction testDirs[2];
611 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000612 return false;
613 }
614 const SkPoint* last = pts;
615 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000616 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000617 testRects[0].set(first, SkToS32(last - first));
618 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000619 if (testRects[0].contains(testRects[1])) {
620 if (rects) {
621 rects[0] = testRects[0];
622 rects[1] = testRects[1];
623 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000624 if (dirs) {
625 dirs[0] = testDirs[0];
626 dirs[1] = testDirs[1];
627 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000628 return true;
629 }
630 if (testRects[1].contains(testRects[0])) {
631 if (rects) {
632 rects[0] = testRects[1];
633 rects[1] = testRects[0];
634 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000635 if (dirs) {
636 dirs[0] = testDirs[1];
637 dirs[1] = testDirs[0];
638 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000639 return true;
640 }
641 }
642 return false;
643}
644
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000645int SkPath::countPoints() const {
646 return fPathRef->countPoints();
647}
648
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000649int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkDEBUGCODE(this->validate();)
651
652 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000653 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = SkMin32(max, fPathRef->countPoints());
655 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
656 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657}
658
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000659SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
661 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000662 }
663 return SkPoint::Make(0, 0);
664}
665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666int SkPath::countVerbs() const {
667 return fPathRef->countVerbs();
668}
669
670static inline void copy_verbs_reverse(uint8_t* inorderDst,
671 const uint8_t* reversedSrc,
672 int count) {
673 for (int i = 0; i < count; ++i) {
674 inorderDst[i] = reversedSrc[~i];
675 }
676}
677
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000678int SkPath::getVerbs(uint8_t dst[], int max) const {
679 SkDEBUGCODE(this->validate();)
680
681 SkASSERT(max >= 0);
682 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 int count = SkMin32(max, fPathRef->countVerbs());
684 copy_verbs_reverse(dst, fPathRef->verbs(), count);
685 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000686}
687
reed@google.com294dd7b2011-10-11 11:58:32 +0000688bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 SkDEBUGCODE(this->validate();)
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000692 if (count > 0) {
693 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000698 if (lastPt) {
699 lastPt->set(0, 0);
700 }
701 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000702}
703
704void SkPath::setLastPt(SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 if (count == 0) {
709 this->moveTo(x, y);
710 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000711 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 SkPathRef::Editor ed(&fPathRef);
713 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000714 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 }
716}
717
reed@android.comd252db02009-04-01 18:31:44 +0000718void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000720 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000723 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724}
725
reed@google.com04863fa2011-05-15 04:08:24 +0000726void SkPath::setConvexity(Convexity c) {
727 if (fConvexity != c) {
728 fConvexity = c;
729 GEN_ID_INC;
730 }
731}
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733//////////////////////////////////////////////////////////////////////////////
734// Construction methods
735
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000736#define DIRTY_AFTER_EDIT \
737 do { \
738 fBoundsIsDirty = true; \
739 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000740 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000741 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000742 } while (0)
743
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000744#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
745 do { \
746 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000747 } while (0)
748
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749void SkPath::incReserve(U16CPU inc) {
750 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752 SkDEBUGCODE(this->validate();)
753}
754
755void SkPath::moveTo(SkScalar x, SkScalar y) {
756 SkDEBUGCODE(this->validate();)
757
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000758 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759
reed@google.comd335d1d2012-01-12 18:17:11 +0000760 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000761 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000762
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000763 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000765 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000766 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767}
768
769void SkPath::rMoveTo(SkScalar x, SkScalar y) {
770 SkPoint pt;
771 this->getLastPt(&pt);
772 this->moveTo(pt.fX + x, pt.fY + y);
773}
774
reed@google.comd335d1d2012-01-12 18:17:11 +0000775void SkPath::injectMoveToIfNeeded() {
776 if (fLastMoveToIndex < 0) {
777 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000778 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000779 x = y = 0;
780 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000781 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000782 x = pt.fX;
783 y = pt.fY;
784 }
785 this->moveTo(x, y);
786 }
787}
788
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789void SkPath::lineTo(SkScalar x, SkScalar y) {
790 SkDEBUGCODE(this->validate();)
791
reed@google.comd335d1d2012-01-12 18:17:11 +0000792 this->injectMoveToIfNeeded();
793
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000794 SkPathRef::Editor ed(&fPathRef);
795 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000796 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000798 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000799 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800}
801
802void SkPath::rLineTo(SkScalar x, SkScalar y) {
803 SkPoint pt;
804 this->getLastPt(&pt);
805 this->lineTo(pt.fX + x, pt.fY + y);
806}
807
808void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
809 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000810
reed@google.comd335d1d2012-01-12 18:17:11 +0000811 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000812
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 SkPathRef::Editor ed(&fPathRef);
814 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 pts[0].set(x1, y1);
816 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000817 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000818
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000819 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000820 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821}
822
823void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
824 SkPoint pt;
825 this->getLastPt(&pt);
826 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
827}
828
reed@google.com277c3f82013-05-31 15:17:50 +0000829void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
830 SkScalar w) {
831 // check for <= 0 or NaN with this test
832 if (!(w > 0)) {
833 this->lineTo(x2, y2);
834 } else if (!SkScalarIsFinite(w)) {
835 this->lineTo(x1, y1);
836 this->lineTo(x2, y2);
837 } else if (SK_Scalar1 == w) {
838 this->quadTo(x1, y1, x2, y2);
839 } else {
840 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000841
reed@google.com277c3f82013-05-31 15:17:50 +0000842 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000843
reed@google.com277c3f82013-05-31 15:17:50 +0000844 SkPathRef::Editor ed(&fPathRef);
845 SkPoint* pts = ed.growForConic(w);
846 pts[0].set(x1, y1);
847 pts[1].set(x2, y2);
848 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000849
reed@google.com277c3f82013-05-31 15:17:50 +0000850 GEN_ID_INC;
851 DIRTY_AFTER_EDIT;
852 }
853}
854
855void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
856 SkScalar w) {
857 SkPoint pt;
858 this->getLastPt(&pt);
859 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
860}
861
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
863 SkScalar x3, SkScalar y3) {
864 SkDEBUGCODE(this->validate();)
865
reed@google.comd335d1d2012-01-12 18:17:11 +0000866 this->injectMoveToIfNeeded();
867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000868 SkPathRef::Editor ed(&fPathRef);
869 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 pts[0].set(x1, y1);
871 pts[1].set(x2, y2);
872 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000873 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000875 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000876 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877}
878
879void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
880 SkScalar x3, SkScalar y3) {
881 SkPoint pt;
882 this->getLastPt(&pt);
883 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
884 pt.fX + x3, pt.fY + y3);
885}
886
887void SkPath::close() {
888 SkDEBUGCODE(this->validate();)
889
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000890 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000892 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893 case kLine_Verb:
894 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000895 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000897 case kMove_Verb: {
898 SkPathRef::Editor ed(&fPathRef);
899 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000900 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000902 }
reed@google.com277c3f82013-05-31 15:17:50 +0000903 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000904 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000905 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000906 default:
907 SkASSERT(!"unexpected verb");
908 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 }
910 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000911
912 // signal that we need a moveTo to follow us (unless we're done)
913#if 0
914 if (fLastMoveToIndex >= 0) {
915 fLastMoveToIndex = ~fLastMoveToIndex;
916 }
917#else
918 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
919#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000920}
921
922///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000923
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000924static void assert_known_direction(int dir) {
925 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
926}
927
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928void SkPath::addRect(const SkRect& rect, Direction dir) {
929 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
930}
931
932void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
933 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000934 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000935 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
936 SkAutoDisableDirectionCheck addc(this);
937
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
939
940 this->incReserve(5);
941
942 this->moveTo(left, top);
943 if (dir == kCCW_Direction) {
944 this->lineTo(left, bottom);
945 this->lineTo(right, bottom);
946 this->lineTo(right, top);
947 } else {
948 this->lineTo(right, top);
949 this->lineTo(right, bottom);
950 this->lineTo(left, bottom);
951 }
952 this->close();
953}
954
reed@google.com744faba2012-05-29 19:54:52 +0000955void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
956 SkDEBUGCODE(this->validate();)
957 if (count <= 0) {
958 return;
959 }
960
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000961 SkPathRef::Editor ed(&fPathRef);
962 fLastMoveToIndex = ed.pathRef()->countPoints();
963 uint8_t* vb;
964 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000965 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000966 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000967
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000968 memcpy(p, pts, count * sizeof(SkPoint));
969 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000970 if (count > 1) {
971 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
972 // be 0, the compiler will remove the test/branch entirely.
973 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000974 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000975 } else {
976 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000977 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000978 }
979 }
980 fSegmentMask |= kLine_SegmentMask;
981 }
982 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000983 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000984 }
985
986 GEN_ID_INC;
987 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000988 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000989}
990
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991static void add_corner_arc(SkPath* path, const SkRect& rect,
992 SkScalar rx, SkScalar ry, int startAngle,
993 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000994 // These two asserts are not sufficient, since really we want to know
995 // that the pair of radii (e.g. left and right, or top and bottom) sum
996 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000997 // these conservative asserts.
998 SkASSERT(0 <= rx && rx <= rect.width());
999 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +00001000
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 SkRect r;
1002 r.set(-rx, -ry, rx, ry);
1003
1004 switch (startAngle) {
1005 case 0:
1006 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1007 break;
1008 case 90:
1009 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1010 break;
1011 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1012 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001013 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 }
reed@google.comabf15c12011-01-18 20:35:51 +00001015
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 SkScalar start = SkIntToScalar(startAngle);
1017 SkScalar sweep = SkIntToScalar(90);
1018 if (SkPath::kCCW_Direction == dir) {
1019 start += sweep;
1020 sweep = -sweep;
1021 }
reed@google.comabf15c12011-01-18 20:35:51 +00001022
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 path->arcTo(r, start, sweep, forceMoveTo);
1024}
1025
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001026void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001027 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001028 SkRRect rrect;
1029 rrect.setRectRadii(rect, (const SkVector*) radii);
1030 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001031}
1032
reed@google.com4ed0fb72012-12-12 20:48:18 +00001033void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001034 assert_known_direction(dir);
1035
1036 if (rrect.isEmpty()) {
1037 return;
1038 }
1039
reed@google.com4ed0fb72012-12-12 20:48:18 +00001040 const SkRect& bounds = rrect.getBounds();
1041
1042 if (rrect.isRect()) {
1043 this->addRect(bounds, dir);
1044 } else if (rrect.isOval()) {
1045 this->addOval(bounds, dir);
1046 } else if (rrect.isSimple()) {
1047 const SkVector& rad = rrect.getSimpleRadii();
1048 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1049 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001050 SkAutoPathBoundsUpdate apbu(this, bounds);
1051
1052 if (kCW_Direction == dir) {
1053 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1054 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1055 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1056 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1057 } else {
1058 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1059 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1060 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1061 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1062 }
1063 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001064 }
1065}
1066
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001067bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001068 int count = fPathRef->countVerbs();
1069 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1070 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001071 if (*verbs == kLine_Verb ||
1072 *verbs == kQuad_Verb ||
1073 *verbs == kCubic_Verb) {
1074 return false;
1075 }
1076 ++verbs;
1077 }
1078 return true;
1079}
1080
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001081#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1082
1083void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1084 Direction dir) {
1085 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001086
humper@google.com75e3ca12013-04-08 21:44:11 +00001087 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001088 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001089 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001090 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001091 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1092 return;
1093 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001094
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001095 SkScalar w = rect.width();
1096 SkScalar halfW = SkScalarHalf(w);
1097 SkScalar h = rect.height();
1098 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001099
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001100 if (halfW <= 0 || halfH <= 0) {
1101 return;
1102 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001103
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001104 bool skip_hori = rx >= halfW;
1105 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001106
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001107 if (skip_hori && skip_vert) {
1108 this->addOval(rect, dir);
1109 return;
1110 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001111
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001112 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001113
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001114 SkAutoPathBoundsUpdate apbu(this, rect);
1115 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001116
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001117 if (skip_hori) {
1118 rx = halfW;
1119 } else if (skip_vert) {
1120 ry = halfH;
1121 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001122
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001123 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1124 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001125
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001126 this->incReserve(17);
1127 this->moveTo(rect.fRight - rx, rect.fTop);
1128 if (dir == kCCW_Direction) {
1129 if (!skip_hori) {
1130 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1131 }
1132 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1133 rect.fLeft, rect.fTop + ry - sy,
1134 rect.fLeft, rect.fTop + ry); // top-left
1135 if (!skip_vert) {
1136 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1137 }
1138 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1139 rect.fLeft + rx - sx, rect.fBottom,
1140 rect.fLeft + rx, rect.fBottom); // bot-left
1141 if (!skip_hori) {
1142 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1143 }
1144 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1145 rect.fRight, rect.fBottom - ry + sy,
1146 rect.fRight, rect.fBottom - ry); // bot-right
1147 if (!skip_vert) {
1148 this->lineTo(rect.fRight, rect.fTop + ry);
1149 }
1150 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1151 rect.fRight - rx + sx, rect.fTop,
1152 rect.fRight - rx, rect.fTop); // top-right
1153 } else {
1154 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1155 rect.fRight, rect.fTop + ry - sy,
1156 rect.fRight, rect.fTop + ry); // top-right
1157 if (!skip_vert) {
1158 this->lineTo(rect.fRight, rect.fBottom - ry);
1159 }
1160 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1161 rect.fRight - rx + sx, rect.fBottom,
1162 rect.fRight - rx, rect.fBottom); // bot-right
1163 if (!skip_hori) {
1164 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1165 }
1166 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1167 rect.fLeft, rect.fBottom - ry + sy,
1168 rect.fLeft, rect.fBottom - ry); // bot-left
1169 if (!skip_vert) {
1170 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1171 }
1172 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1173 rect.fLeft + rx - sx, rect.fTop,
1174 rect.fLeft + rx, rect.fTop); // top-left
1175 if (!skip_hori) {
1176 this->lineTo(rect.fRight - rx, rect.fTop); // top
1177 }
1178 }
1179 this->close();
1180}
1181
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001183 assert_known_direction(dir);
1184
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001185 /* If addOval() is called after previous moveTo(),
1186 this path is still marked as an oval. This is used to
1187 fit into WebKit's calling sequences.
1188 We can't simply check isEmpty() in this case, as additional
1189 moveTo() would mark the path non empty.
1190 */
1191 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001192 if (fIsOval) {
1193 fDirection = dir;
1194 } else {
1195 fDirection = kUnknown_Direction;
1196 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197
1198 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001199 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001200
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201 SkAutoPathBoundsUpdate apbu(this, oval);
1202
1203 SkScalar cx = oval.centerX();
1204 SkScalar cy = oval.centerY();
1205 SkScalar rx = SkScalarHalf(oval.width());
1206 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1209 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1210 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1211 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1212
1213 /*
1214 To handle imprecision in computing the center and radii, we revert to
1215 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1216 to ensure that we don't exceed the oval's bounds *ever*, since we want
1217 to use oval for our fast-bounds, rather than have to recompute it.
1218 */
1219 const SkScalar L = oval.fLeft; // cx - rx
1220 const SkScalar T = oval.fTop; // cy - ry
1221 const SkScalar R = oval.fRight; // cx + rx
1222 const SkScalar B = oval.fBottom; // cy + ry
1223
1224 this->incReserve(17); // 8 quads + close
1225 this->moveTo(R, cy);
1226 if (dir == kCCW_Direction) {
1227 this->quadTo( R, cy - sy, cx + mx, cy - my);
1228 this->quadTo(cx + sx, T, cx , T);
1229 this->quadTo(cx - sx, T, cx - mx, cy - my);
1230 this->quadTo( L, cy - sy, L, cy );
1231 this->quadTo( L, cy + sy, cx - mx, cy + my);
1232 this->quadTo(cx - sx, B, cx , B);
1233 this->quadTo(cx + sx, B, cx + mx, cy + my);
1234 this->quadTo( R, cy + sy, R, cy );
1235 } else {
1236 this->quadTo( R, cy + sy, cx + mx, cy + my);
1237 this->quadTo(cx + sx, B, cx , B);
1238 this->quadTo(cx - sx, B, cx - mx, cy + my);
1239 this->quadTo( L, cy + sy, L, cy );
1240 this->quadTo( L, cy - sy, cx - mx, cy - my);
1241 this->quadTo(cx - sx, T, cx , T);
1242 this->quadTo(cx + sx, T, cx + mx, cy - my);
1243 this->quadTo( R, cy - sy, R, cy );
1244 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 this->close();
1246}
1247
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001248bool SkPath::isOval(SkRect* rect) const {
1249 if (fIsOval && rect) {
1250 *rect = getBounds();
1251 }
1252
1253 return fIsOval;
1254}
1255
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1257 if (r > 0) {
1258 SkRect rect;
1259 rect.set(x - r, y - r, x + r, y + r);
1260 this->addOval(rect, dir);
1261 }
1262}
1263
1264#include "SkGeometry.h"
1265
1266static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1267 SkScalar sweepAngle,
1268 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001269
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001270 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001271 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1272 // Chrome uses this path to move into and out of ovals. If not
1273 // treated as a special case the moves can distort the oval's
1274 // bounding box (and break the circle special case).
1275 pts[0].set(oval.fRight, oval.centerY());
1276 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001277 } else if (0 == oval.width() && 0 == oval.height()) {
1278 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001279 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001280 // a rect.
1281 // TODO: optimizing the case where only one of width or height is zero
1282 // should also be considered. This case, however, doesn't seem to be
1283 // as common as the single point case.
1284 pts[0].set(oval.fRight, oval.fTop);
1285 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001286 }
1287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 SkVector start, stop;
1289
1290 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1291 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1292 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001293
1294 /* If the sweep angle is nearly (but less than) 360, then due to precision
1295 loss in radians-conversion and/or sin/cos, we may end up with coincident
1296 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1297 of drawing a nearly complete circle (good).
1298 e.g. canvas.drawArc(0, 359.99, ...)
1299 -vs- canvas.drawArc(0, 359.9, ...)
1300 We try to detect this edge case, and tweak the stop vector
1301 */
1302 if (start == stop) {
1303 SkScalar sw = SkScalarAbs(sweepAngle);
1304 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1305 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1306 // make a guess at a tiny angle (in radians) to tweak by
1307 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1308 // not sure how much will be enough, so we use a loop
1309 do {
1310 stopRad -= deltaRad;
1311 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1312 } while (start == stop);
1313 }
1314 }
1315
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1319 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001320
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 return SkBuildQuadArc(start, stop,
1322 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1323 &matrix, pts);
1324}
1325
1326void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1327 bool forceMoveTo) {
1328 if (oval.width() < 0 || oval.height() < 0) {
1329 return;
1330 }
1331
1332 SkPoint pts[kSkBuildQuadArcStorage];
1333 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1334 SkASSERT((count & 1) == 1);
1335
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001336 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 forceMoveTo = true;
1338 }
1339 this->incReserve(count);
1340 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1341 for (int i = 1; i < count; i += 2) {
1342 this->quadTo(pts[i], pts[i+1]);
1343 }
1344}
1345
1346void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1347 SkScalar sweepAngle) {
1348 if (oval.isEmpty() || 0 == sweepAngle) {
1349 return;
1350 }
1351
1352 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1353
1354 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1355 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1356 return;
1357 }
1358
1359 SkPoint pts[kSkBuildQuadArcStorage];
1360 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1361
1362 this->incReserve(count);
1363 this->moveTo(pts[0]);
1364 for (int i = 1; i < count; i += 2) {
1365 this->quadTo(pts[i], pts[i+1]);
1366 }
1367}
1368
1369/*
1370 Need to handle the case when the angle is sharp, and our computed end-points
1371 for the arc go behind pt1 and/or p2...
1372*/
1373void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1374 SkScalar radius) {
1375 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001376
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 // need to know our prev pt so we can construct tangent vectors
1378 {
1379 SkPoint start;
1380 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001381 // Handle degenerate cases by adding a line to the first point and
1382 // bailing out.
1383 if ((x1 == start.fX && y1 == start.fY) ||
1384 (x1 == x2 && y1 == y2) ||
1385 radius == 0) {
1386 this->lineTo(x1, y1);
1387 return;
1388 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 before.setNormalize(x1 - start.fX, y1 - start.fY);
1390 after.setNormalize(x2 - x1, y2 - y1);
1391 }
reed@google.comabf15c12011-01-18 20:35:51 +00001392
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393 SkScalar cosh = SkPoint::DotProduct(before, after);
1394 SkScalar sinh = SkPoint::CrossProduct(before, after);
1395
1396 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001397 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 return;
1399 }
reed@google.comabf15c12011-01-18 20:35:51 +00001400
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1402 if (dist < 0) {
1403 dist = -dist;
1404 }
1405
1406 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1407 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1408 SkRotationDirection arcDir;
1409
1410 // now turn before/after into normals
1411 if (sinh > 0) {
1412 before.rotateCCW();
1413 after.rotateCCW();
1414 arcDir = kCW_SkRotationDirection;
1415 } else {
1416 before.rotateCW();
1417 after.rotateCW();
1418 arcDir = kCCW_SkRotationDirection;
1419 }
1420
1421 SkMatrix matrix;
1422 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001423
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 matrix.setScale(radius, radius);
1425 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1426 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001427
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 this->incReserve(count);
1431 // [xx,yy] == pts[0]
1432 this->lineTo(xx, yy);
1433 for (int i = 1; i < count; i += 2) {
1434 this->quadTo(pts[i], pts[i+1]);
1435 }
1436}
1437
1438///////////////////////////////////////////////////////////////////////////////
1439
1440void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1441 SkMatrix matrix;
1442
1443 matrix.setTranslate(dx, dy);
1444 this->addPath(path, matrix);
1445}
1446
1447void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001448 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001450 fIsOval = false;
1451
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001452 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 SkPoint pts[4];
1454 Verb verb;
1455
1456 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1457
1458 while ((verb = iter.next(pts)) != kDone_Verb) {
1459 switch (verb) {
1460 case kMove_Verb:
1461 proc(matrix, &pts[0], &pts[0], 1);
1462 this->moveTo(pts[0]);
1463 break;
1464 case kLine_Verb:
1465 proc(matrix, &pts[1], &pts[1], 1);
1466 this->lineTo(pts[1]);
1467 break;
1468 case kQuad_Verb:
1469 proc(matrix, &pts[1], &pts[1], 2);
1470 this->quadTo(pts[1], pts[2]);
1471 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001472 case kConic_Verb:
1473 proc(matrix, &pts[1], &pts[1], 2);
1474 this->conicTo(pts[1], pts[2], iter.conicWeight());
1475 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 case kCubic_Verb:
1477 proc(matrix, &pts[1], &pts[1], 3);
1478 this->cubicTo(pts[1], pts[2], pts[3]);
1479 break;
1480 case kClose_Verb:
1481 this->close();
1482 break;
1483 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001484 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 }
1486 }
1487}
1488
1489///////////////////////////////////////////////////////////////////////////////
1490
reed@google.com277c3f82013-05-31 15:17:50 +00001491static int pts_in_verb(unsigned verb) {
1492 static const uint8_t gPtsInVerb[] = {
1493 1, // kMove
1494 1, // kLine
1495 2, // kQuad
1496 2, // kConic
1497 3, // kCubic
1498 0, // kClose
1499 0 // kDone
1500 };
1501
1502 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1503 return gPtsInVerb[verb];
1504}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505
1506// ignore the initial moveto, and stop when the 1st contour ends
1507void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001508 int i, vcount = path.fPathRef->countVerbs();
1509 // exit early if the path is empty, or just has a moveTo.
1510 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 return;
1512 }
1513
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001516 fIsOval = false;
1517
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 const uint8_t* verbs = path.fPathRef->verbs();
1519 // skip the initial moveTo
1520 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001521 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 case kLine_Verb:
1527 this->lineTo(pts[0].fX, pts[0].fY);
1528 break;
1529 case kQuad_Verb:
1530 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1531 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001532 case kConic_Verb:
1533 this->conicTo(pts[0], pts[1], *conicWeight++);
1534 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001536 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 +00001537 break;
1538 case kClose_Verb:
1539 return;
1540 }
reed@google.com277c3f82013-05-31 15:17:50 +00001541 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 }
1543}
1544
1545// ignore the last point of the 1st contour
1546void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 int i, vcount = path.fPathRef->countVerbs();
1548 // exit early if the path is empty, or just has a moveTo.
1549 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 return;
1551 }
1552
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001555 fIsOval = false;
1556
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 const uint8_t* verbs = path.fPathRef->verbs();
1558 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001559 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001561 SkASSERT(verbs[~0] == kMove_Verb);
1562 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001563 unsigned v = verbs[~i];
1564 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 if (n == 0) {
1566 break;
1567 }
1568 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001569 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570 }
1571
1572 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001573 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 case kLine_Verb:
1575 this->lineTo(pts[-1].fX, pts[-1].fY);
1576 break;
1577 case kQuad_Verb:
1578 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1579 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001580 case kConic_Verb:
1581 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1582 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 case kCubic_Verb:
1584 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1585 pts[-3].fX, pts[-3].fY);
1586 break;
1587 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001588 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 break;
1590 }
reed@google.com277c3f82013-05-31 15:17:50 +00001591 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 }
1593}
1594
reed@google.com63d73742012-01-10 15:33:12 +00001595void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001596 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001597
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001598 const SkPoint* pts = src.fPathRef->pointsEnd();
1599 // we will iterator through src's verbs backwards
1600 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1601 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001602 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001603
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001604 fIsOval = false;
1605
reed@google.com63d73742012-01-10 15:33:12 +00001606 bool needMove = true;
1607 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001608 while (verbs < verbsEnd) {
1609 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001610 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001611
1612 if (needMove) {
1613 --pts;
1614 this->moveTo(pts->fX, pts->fY);
1615 needMove = false;
1616 }
1617 pts -= n;
1618 switch (v) {
1619 case kMove_Verb:
1620 if (needClose) {
1621 this->close();
1622 needClose = false;
1623 }
1624 needMove = true;
1625 pts += 1; // so we see the point in "if (needMove)" above
1626 break;
1627 case kLine_Verb:
1628 this->lineTo(pts[0]);
1629 break;
1630 case kQuad_Verb:
1631 this->quadTo(pts[1], pts[0]);
1632 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001633 case kConic_Verb:
1634 this->conicTo(pts[1], pts[0], *--conicWeights);
1635 break;
reed@google.com63d73742012-01-10 15:33:12 +00001636 case kCubic_Verb:
1637 this->cubicTo(pts[2], pts[1], pts[0]);
1638 break;
1639 case kClose_Verb:
1640 needClose = true;
1641 break;
1642 default:
1643 SkASSERT(!"unexpected verb");
1644 }
1645 }
1646}
1647
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648///////////////////////////////////////////////////////////////////////////////
1649
1650void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1651 SkMatrix matrix;
1652
1653 matrix.setTranslate(dx, dy);
1654 this->transform(matrix, dst);
1655}
1656
1657#include "SkGeometry.h"
1658
1659static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1660 int level = 2) {
1661 if (--level >= 0) {
1662 SkPoint tmp[5];
1663
1664 SkChopQuadAtHalf(pts, tmp);
1665 subdivide_quad_to(path, &tmp[0], level);
1666 subdivide_quad_to(path, &tmp[2], level);
1667 } else {
1668 path->quadTo(pts[1], pts[2]);
1669 }
1670}
1671
1672static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1673 int level = 2) {
1674 if (--level >= 0) {
1675 SkPoint tmp[7];
1676
1677 SkChopCubicAtHalf(pts, tmp);
1678 subdivide_cubic_to(path, &tmp[0], level);
1679 subdivide_cubic_to(path, &tmp[3], level);
1680 } else {
1681 path->cubicTo(pts[1], pts[2], pts[3]);
1682 }
1683}
1684
1685void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1686 SkDEBUGCODE(this->validate();)
1687 if (dst == NULL) {
1688 dst = (SkPath*)this;
1689 }
1690
tomhudson@google.com8d430182011-06-06 19:11:19 +00001691 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 SkPath tmp;
1693 tmp.fFillType = fFillType;
1694
1695 SkPath::Iter iter(*this, false);
1696 SkPoint pts[4];
1697 SkPath::Verb verb;
1698
reed@google.com4a3b7142012-05-16 17:16:46 +00001699 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 switch (verb) {
1701 case kMove_Verb:
1702 tmp.moveTo(pts[0]);
1703 break;
1704 case kLine_Verb:
1705 tmp.lineTo(pts[1]);
1706 break;
1707 case kQuad_Verb:
1708 subdivide_quad_to(&tmp, pts);
1709 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001710 case kConic_Verb:
1711 SkASSERT(!"TODO: compute new weight");
1712 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1713 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 case kCubic_Verb:
1715 subdivide_cubic_to(&tmp, pts);
1716 break;
1717 case kClose_Verb:
1718 tmp.close();
1719 break;
1720 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001721 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 break;
1723 }
1724 }
1725
1726 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001727 SkPathRef::Editor ed(&dst->fPathRef);
1728 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001729 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001731 /*
1732 * If we're not in perspective, we can transform all of the points at
1733 * once.
1734 *
1735 * Here we also want to optimize bounds, by noting if the bounds are
1736 * already known, and if so, we just transform those as well and mark
1737 * them as "known", rather than force the transformed path to have to
1738 * recompute them.
1739 *
1740 * Special gotchas if the path is effectively empty (<= 1 point) or
1741 * if it is non-finite. In those cases bounds need to stay empty,
1742 * regardless of the matrix.
1743 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001744 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001745 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001746 if (fIsFinite) {
1747 matrix.mapRect(&dst->fBounds, fBounds);
1748 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1749 dst->fBounds.setEmpty();
1750 }
1751 } else {
1752 dst->fIsFinite = false;
1753 dst->fBounds.setEmpty();
1754 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001756 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001757 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 }
1759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1761
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001764 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001765 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001767
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001768#ifdef SK_BUILD_FOR_ANDROID
1769 if (!matrix.isIdentity()) {
1770 GEN_ID_PTR_INC(dst);
1771 }
1772#endif
1773
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001774 if (kUnknown_Direction == fDirection) {
1775 dst->fDirection = kUnknown_Direction;
1776 } else {
1777 SkScalar det2x2 =
1778 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1779 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1780 if (det2x2 < 0) {
1781 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1782 } else if (det2x2 > 0) {
1783 dst->fDirection = fDirection;
1784 } else {
1785 dst->fDirection = kUnknown_Direction;
1786 }
1787 }
1788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 // It's an oval only if it stays a rect.
1790 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001791
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 SkDEBUGCODE(dst->validate();)
1793 }
1794}
1795
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796///////////////////////////////////////////////////////////////////////////////
1797///////////////////////////////////////////////////////////////////////////////
1798
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001799enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001800 kEmptyContour_SegmentState, // The current contour is empty. We may be
1801 // starting processing or we may have just
1802 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001803 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1804 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1805 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806};
1807
1808SkPath::Iter::Iter() {
1809#ifdef SK_DEBUG
1810 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001811 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001814 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815#endif
1816 // need to init enough to make next() harmlessly return kDone_Verb
1817 fVerbs = NULL;
1818 fVerbStop = NULL;
1819 fNeedClose = false;
1820}
1821
1822SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1823 this->setPath(path, forceClose);
1824}
1825
1826void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 fPts = path.fPathRef->points();
1828 fVerbs = path.fPathRef->verbs();
1829 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001830 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001832 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 fForceClose = SkToU8(forceClose);
1834 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001835 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836}
1837
1838bool SkPath::Iter::isClosedContour() const {
1839 if (fVerbs == NULL || fVerbs == fVerbStop) {
1840 return false;
1841 }
1842 if (fForceClose) {
1843 return true;
1844 }
1845
1846 const uint8_t* verbs = fVerbs;
1847 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001848
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001849 if (kMove_Verb == *(verbs - 1)) {
1850 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 }
1852
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001853 while (verbs > stop) {
1854 // verbs points one beyond the current verb, decrement first.
1855 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 if (kMove_Verb == v) {
1857 break;
1858 }
1859 if (kClose_Verb == v) {
1860 return true;
1861 }
1862 }
1863 return false;
1864}
1865
1866SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001867 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001869 // A special case: if both points are NaN, SkPoint::operation== returns
1870 // false, but the iterator expects that they are treated as the same.
1871 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001872 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1873 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001874 return kClose_Verb;
1875 }
1876
reed@google.com9e25dbf2012-05-15 17:05:38 +00001877 pts[0] = fLastPt;
1878 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 fLastPt = fMoveTo;
1880 fCloseLine = true;
1881 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001882 } else {
1883 pts[0] = fMoveTo;
1884 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886}
1887
reed@google.com9e25dbf2012-05-15 17:05:38 +00001888const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 if (fSegmentState == kAfterMove_SegmentState) {
1890 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1895 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001896 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898}
1899
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900void SkPath::Iter::consumeDegenerateSegments() {
1901 // We need to step over anything that will not move the current draw point
1902 // forward before the next move is seen
1903 const uint8_t* lastMoveVerb = 0;
1904 const SkPoint* lastMovePt = 0;
1905 SkPoint lastPt = fLastPt;
1906 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001907 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 switch (verb) {
1909 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 // Keep a record of this most recent move
1911 lastMoveVerb = fVerbs;
1912 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001913 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 fPts++;
1916 break;
1917
1918 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001919 // A close when we are in a segment is always valid except when it
1920 // follows a move which follows a segment.
1921 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 return;
1923 }
1924 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 break;
1927
1928 case kLine_Verb:
1929 if (!IsLineDegenerate(lastPt, fPts[0])) {
1930 if (lastMoveVerb) {
1931 fVerbs = lastMoveVerb;
1932 fPts = lastMovePt;
1933 return;
1934 }
1935 return;
1936 }
1937 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001938 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 fPts++;
1940 break;
1941
reed@google.com277c3f82013-05-31 15:17:50 +00001942 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 case kQuad_Verb:
1944 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1945 if (lastMoveVerb) {
1946 fVerbs = lastMoveVerb;
1947 fPts = lastMovePt;
1948 return;
1949 }
1950 return;
1951 }
1952 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001953 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001955 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 break;
1957
1958 case kCubic_Verb:
1959 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1960 if (lastMoveVerb) {
1961 fVerbs = lastMoveVerb;
1962 fPts = lastMovePt;
1963 return;
1964 }
1965 return;
1966 }
1967 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001968 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 fPts += 3;
1970 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001971
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001973 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 }
1975 }
1976}
1977
reed@google.com4a3b7142012-05-16 17:16:46 +00001978SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001979 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 // Close the curve if requested and if there is some curve to close
1983 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 return kLine_Verb;
1986 }
1987 fNeedClose = false;
1988 return kClose_Verb;
1989 }
1990 return kDone_Verb;
1991 }
1992
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001993 // fVerbs is one beyond the current verb, decrement first
1994 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001995 const SkPoint* SK_RESTRICT srcPts = fPts;
1996 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997
1998 switch (verb) {
1999 case kMove_Verb:
2000 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002001 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 verb = this->autoClose(pts);
2003 if (verb == kClose_Verb) {
2004 fNeedClose = false;
2005 }
2006 return (Verb)verb;
2007 }
2008 if (fVerbs == fVerbStop) { // might be a trailing moveto
2009 return kDone_Verb;
2010 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002011 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002012 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002014 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002015 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 fNeedClose = fForceClose;
2017 break;
2018 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002019 pts[0] = this->cons_moveTo();
2020 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021 fLastPt = srcPts[0];
2022 fCloseLine = false;
2023 srcPts += 1;
2024 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002025 case kConic_Verb:
2026 fConicWeights += 1;
2027 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002029 pts[0] = this->cons_moveTo();
2030 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 fLastPt = srcPts[1];
2032 srcPts += 2;
2033 break;
2034 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002035 pts[0] = this->cons_moveTo();
2036 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 fLastPt = srcPts[2];
2038 srcPts += 3;
2039 break;
2040 case kClose_Verb:
2041 verb = this->autoClose(pts);
2042 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002043 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 } else {
2045 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002046 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002048 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 break;
2050 }
2051 fPts = srcPts;
2052 return (Verb)verb;
2053}
2054
2055///////////////////////////////////////////////////////////////////////////////
2056
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002057SkPath::RawIter::RawIter() {
2058#ifdef SK_DEBUG
2059 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002060 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002061 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2062#endif
2063 // need to init enough to make next() harmlessly return kDone_Verb
2064 fVerbs = NULL;
2065 fVerbStop = NULL;
2066}
2067
2068SkPath::RawIter::RawIter(const SkPath& path) {
2069 this->setPath(path);
2070}
2071
2072void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002073 fPts = path.fPathRef->points();
2074 fVerbs = path.fPathRef->verbs();
2075 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002076 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002077 fMoveTo.fX = fMoveTo.fY = 0;
2078 fLastPt.fX = fLastPt.fY = 0;
2079}
2080
2081SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002082 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002083 if (fVerbs == fVerbStop) {
2084 return kDone_Verb;
2085 }
2086
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002087 // fVerbs points one beyond next verb so decrement first.
2088 unsigned verb = *(--fVerbs);
2089 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002090
2091 switch (verb) {
2092 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002093 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002094 fMoveTo = srcPts[0];
2095 fLastPt = fMoveTo;
2096 srcPts += 1;
2097 break;
2098 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002099 pts[0] = fLastPt;
2100 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002101 fLastPt = srcPts[0];
2102 srcPts += 1;
2103 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002104 case kConic_Verb:
2105 fConicWeights += 1;
2106 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002107 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002108 pts[0] = fLastPt;
2109 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002110 fLastPt = srcPts[1];
2111 srcPts += 2;
2112 break;
2113 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002114 pts[0] = fLastPt;
2115 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002116 fLastPt = srcPts[2];
2117 srcPts += 3;
2118 break;
2119 case kClose_Verb:
2120 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002121 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002122 break;
2123 }
2124 fPts = srcPts;
2125 return (Verb)verb;
2126}
2127
2128///////////////////////////////////////////////////////////////////////////////
2129
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002131 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132*/
2133
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002134uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 SkDEBUGCODE(this->validate();)
2136
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002137 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002138 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002139 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002140 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002141 return SkAlign4(byteCount);
2142 }
2143
2144 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002145
2146 // Call getBounds() to ensure (as a side-effect) that fBounds
2147 // and fIsFinite are computed.
2148 const SkRect& bounds = this->getBounds();
2149 SkASSERT(!fBoundsIsDirty);
2150
2151 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2152 ((fIsOval & 1) << kIsOval_SerializationShift) |
2153 (fConvexity << kConvexity_SerializationShift) |
2154 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002155 (fSegmentMask << kSegmentMask_SerializationShift) |
2156 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002157
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002158 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002159
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002160 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002161
2162 buffer.write(&bounds, sizeof(bounds));
2163
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002164 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002165 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166}
2167
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002168uint32_t SkPath::readFromMemory(const void* storage) {
2169 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002170
reed@google.com98b11f12011-09-21 18:40:27 +00002171 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002172 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2173 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2174 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2175 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002176 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002177 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002178
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002179 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002180
2181 buffer.read(&fBounds, sizeof(fBounds));
2182 fBoundsIsDirty = false;
2183
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002184 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002185
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002186 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187
2188 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002189 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190}
2191
2192///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193
reed@google.com51bbe752013-01-17 22:07:50 +00002194#include "SkString.h"
2195
2196static void append_scalar(SkString* str, SkScalar value) {
2197 SkString tmp;
2198 tmp.printf("%g", value);
2199 if (tmp.contains('.')) {
2200 tmp.appendUnichar('f');
2201 }
2202 str->append(tmp);
2203}
2204
2205static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002206 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002207 str->append(label);
2208 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002209
reed@google.com51bbe752013-01-17 22:07:50 +00002210 const SkScalar* values = &pts[0].fX;
2211 count *= 2;
2212
2213 for (int i = 0; i < count; ++i) {
2214 append_scalar(str, values[i]);
2215 if (i < count - 1) {
2216 str->append(", ");
2217 }
2218 }
reed@google.com277c3f82013-05-31 15:17:50 +00002219 if (conicWeight >= 0) {
2220 str->append(", ");
2221 append_scalar(str, conicWeight);
2222 }
reed@google.com51bbe752013-01-17 22:07:50 +00002223 str->append(");\n");
2224}
2225
reed@android.com8a1c16f2008-12-17 15:59:43 +00002226void SkPath::dump(bool forceClose, const char title[]) const {
2227 Iter iter(*this, forceClose);
2228 SkPoint pts[4];
2229 Verb verb;
2230
2231 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2232 title ? title : "");
2233
reed@google.com51bbe752013-01-17 22:07:50 +00002234 SkString builder;
2235
reed@google.com4a3b7142012-05-16 17:16:46 +00002236 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002237 switch (verb) {
2238 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002239 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002240 break;
2241 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002242 append_params(&builder, "path.lineTo", &pts[1], 1);
2243 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002244 break;
2245 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002246 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002247 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002248 case kConic_Verb:
2249 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2250 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002251 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002252 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002253 break;
2254 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002255 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002256 break;
2257 default:
2258 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2259 verb = kDone_Verb; // stop the loop
2260 break;
2261 }
2262 }
reed@google.com51bbe752013-01-17 22:07:50 +00002263 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002264}
2265
reed@android.come522ca52009-11-23 20:10:41 +00002266void SkPath::dump() const {
2267 this->dump(false);
2268}
2269
2270#ifdef SK_DEBUG
2271void SkPath::validate() const {
2272 SkASSERT(this != NULL);
2273 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002274
djsollen@google.com077348c2012-10-22 20:23:32 +00002275#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002276 if (!fBoundsIsDirty) {
2277 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002278
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002279 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002280 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002281
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002282 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002283 // if we're empty, fBounds may be empty but translated, so we can't
2284 // necessarily compare to bounds directly
2285 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2286 // be [2, 2, 2, 2]
2287 SkASSERT(bounds.isEmpty());
2288 SkASSERT(fBounds.isEmpty());
2289 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002290 if (bounds.isEmpty()) {
2291 SkASSERT(fBounds.isEmpty());
2292 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002293 if (!fBounds.isEmpty()) {
2294 SkASSERT(fBounds.contains(bounds));
2295 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002296 }
reed@android.come522ca52009-11-23 20:10:41 +00002297 }
2298 }
reed@google.com10296cc2011-09-21 12:29:05 +00002299
2300 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002301 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2302 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2303 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002304 case kLine_Verb:
2305 mask |= kLine_SegmentMask;
2306 break;
2307 case kQuad_Verb:
2308 mask |= kQuad_SegmentMask;
2309 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002310 case kConic_Verb:
2311 mask |= kConic_SegmentMask;
2312 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002313 case kCubic_Verb:
2314 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002315 case kMove_Verb: // these verbs aren't included in the segment mask.
2316 case kClose_Verb:
2317 break;
2318 case kDone_Verb:
2319 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2320 break;
2321 default:
2322 SkDEBUGFAIL("Unknown Verb");
2323 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002324 }
2325 }
2326 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002327#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002328}
djsollen@google.com077348c2012-10-22 20:23:32 +00002329#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002330
reed@google.com04863fa2011-05-15 04:08:24 +00002331///////////////////////////////////////////////////////////////////////////////
2332
reed@google.com0b7b9822011-05-16 12:29:27 +00002333static int sign(SkScalar x) { return x < 0; }
2334#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002335
reed@google.com04863fa2011-05-15 04:08:24 +00002336static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002337 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002338}
2339
2340// only valid for a single contour
2341struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002342 Convexicator()
2343 : fPtCount(0)
2344 , fConvexity(SkPath::kConvex_Convexity)
2345 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002346 fSign = 0;
2347 // warnings
2348 fCurrPt.set(0, 0);
2349 fVec0.set(0, 0);
2350 fVec1.set(0, 0);
2351 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002352
2353 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002354 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 }
2356
2357 SkPath::Convexity getConvexity() const { return fConvexity; }
2358
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002359 /** The direction returned is only valid if the path is determined convex */
2360 SkPath::Direction getDirection() const { return fDirection; }
2361
reed@google.com04863fa2011-05-15 04:08:24 +00002362 void addPt(const SkPoint& pt) {
2363 if (SkPath::kConcave_Convexity == fConvexity) {
2364 return;
2365 }
2366
2367 if (0 == fPtCount) {
2368 fCurrPt = pt;
2369 ++fPtCount;
2370 } else {
2371 SkVector vec = pt - fCurrPt;
2372 if (vec.fX || vec.fY) {
2373 fCurrPt = pt;
2374 if (++fPtCount == 2) {
2375 fFirstVec = fVec1 = vec;
2376 } else {
2377 SkASSERT(fPtCount > 2);
2378 this->addVec(vec);
2379 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002380
reed@google.com85b6e392011-05-15 20:25:17 +00002381 int sx = sign(vec.fX);
2382 int sy = sign(vec.fY);
2383 fDx += (sx != fSx);
2384 fDy += (sy != fSy);
2385 fSx = sx;
2386 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002387
reed@google.com85b6e392011-05-15 20:25:17 +00002388 if (fDx > 3 || fDy > 3) {
2389 fConvexity = SkPath::kConcave_Convexity;
2390 }
reed@google.com04863fa2011-05-15 04:08:24 +00002391 }
2392 }
2393 }
2394
2395 void close() {
2396 if (fPtCount > 2) {
2397 this->addVec(fFirstVec);
2398 }
2399 }
2400
2401private:
2402 void addVec(const SkVector& vec) {
2403 SkASSERT(vec.fX || vec.fY);
2404 fVec0 = fVec1;
2405 fVec1 = vec;
2406 int sign = CrossProductSign(fVec0, fVec1);
2407 if (0 == fSign) {
2408 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409 if (1 == sign) {
2410 fDirection = SkPath::kCW_Direction;
2411 } else if (-1 == sign) {
2412 fDirection = SkPath::kCCW_Direction;
2413 }
reed@google.com04863fa2011-05-15 04:08:24 +00002414 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002415 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002416 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002417 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 }
2419 }
2420 }
2421
2422 SkPoint fCurrPt;
2423 SkVector fVec0, fVec1, fFirstVec;
2424 int fPtCount; // non-degenerate points
2425 int fSign;
2426 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002427 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002428 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002429};
2430
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431SkPath::Convexity SkPath::internalGetConvexity() const {
2432 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002433 SkPoint pts[4];
2434 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002436
2437 int contourCount = 0;
2438 int count;
2439 Convexicator state;
2440
2441 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2442 switch (verb) {
2443 case kMove_Verb:
2444 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002445 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002446 return kConcave_Convexity;
2447 }
2448 pts[1] = pts[0];
2449 count = 1;
2450 break;
2451 case kLine_Verb: count = 1; break;
2452 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002453 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002454 case kCubic_Verb: count = 3; break;
2455 case kClose_Verb:
2456 state.close();
2457 count = 0;
2458 break;
2459 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002460 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002461 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002462 return kConcave_Convexity;
2463 }
2464
2465 for (int i = 1; i <= count; i++) {
2466 state.addPt(pts[i]);
2467 }
2468 // early exit
2469 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002470 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002471 return kConcave_Convexity;
2472 }
2473 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002474 fConvexity = state.getConvexity();
2475 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2476 fDirection = state.getDirection();
2477 }
2478 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002479}
reed@google.com69a99432012-01-10 18:00:10 +00002480
2481///////////////////////////////////////////////////////////////////////////////
2482
2483class ContourIter {
2484public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002485 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002486
2487 bool done() const { return fDone; }
2488 // if !done() then these may be called
2489 int count() const { return fCurrPtCount; }
2490 const SkPoint* pts() const { return fCurrPt; }
2491 void next();
2492
2493private:
2494 int fCurrPtCount;
2495 const SkPoint* fCurrPt;
2496 const uint8_t* fCurrVerb;
2497 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002498 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002499 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002500 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002501};
2502
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002503ContourIter::ContourIter(const SkPathRef& pathRef) {
2504 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002505 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002506 fCurrPt = pathRef.points();
2507 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002508 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002509 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002510 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002511 this->next();
2512}
2513
2514void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002515 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002516 fDone = true;
2517 }
2518 if (fDone) {
2519 return;
2520 }
2521
2522 // skip pts of prev contour
2523 fCurrPt += fCurrPtCount;
2524
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002525 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002526 int ptCount = 1; // moveTo
2527 const uint8_t* verbs = fCurrVerb;
2528
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002529 for (--verbs; verbs > fStopVerbs; --verbs) {
2530 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002531 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002532 goto CONTOUR_END;
2533 case SkPath::kLine_Verb:
2534 ptCount += 1;
2535 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002536 case SkPath::kConic_Verb:
2537 fCurrConicWeight += 1;
2538 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002539 case SkPath::kQuad_Verb:
2540 ptCount += 2;
2541 break;
2542 case SkPath::kCubic_Verb:
2543 ptCount += 3;
2544 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002545 case SkPath::kClose_Verb:
2546 break;
2547 default:
2548 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002549 break;
2550 }
2551 }
2552CONTOUR_END:
2553 fCurrPtCount = ptCount;
2554 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002555 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002556}
2557
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002558// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002559static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002560 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2561 // We may get 0 when the above subtracts underflow. We expect this to be
2562 // very rare and lazily promote to double.
2563 if (0 == cross) {
2564 double p0x = SkScalarToDouble(p0.fX);
2565 double p0y = SkScalarToDouble(p0.fY);
2566
2567 double p1x = SkScalarToDouble(p1.fX);
2568 double p1y = SkScalarToDouble(p1.fY);
2569
2570 double p2x = SkScalarToDouble(p2.fX);
2571 double p2y = SkScalarToDouble(p2.fY);
2572
2573 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2574 (p1y - p0y) * (p2x - p0x));
2575
2576 }
2577 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002578}
2579
reed@google.comc1ea60a2012-01-31 15:15:36 +00002580// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002581static int find_max_y(const SkPoint pts[], int count) {
2582 SkASSERT(count > 0);
2583 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002584 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002585 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002586 SkScalar y = pts[i].fY;
2587 if (y > max) {
2588 max = y;
2589 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002590 }
2591 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002592 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002593}
2594
reed@google.comcabaf1d2012-01-11 21:03:05 +00002595static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2596 int i = index;
2597 for (;;) {
2598 i = (i + inc) % n;
2599 if (i == index) { // we wrapped around, so abort
2600 break;
2601 }
2602 if (pts[index] != pts[i]) { // found a different point, success!
2603 break;
2604 }
2605 }
2606 return i;
2607}
2608
reed@google.comc1ea60a2012-01-31 15:15:36 +00002609/**
2610 * Starting at index, and moving forward (incrementing), find the xmin and
2611 * xmax of the contiguous points that have the same Y.
2612 */
2613static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2614 int* maxIndexPtr) {
2615 const SkScalar y = pts[index].fY;
2616 SkScalar min = pts[index].fX;
2617 SkScalar max = min;
2618 int minIndex = index;
2619 int maxIndex = index;
2620 for (int i = index + 1; i < n; ++i) {
2621 if (pts[i].fY != y) {
2622 break;
2623 }
2624 SkScalar x = pts[i].fX;
2625 if (x < min) {
2626 min = x;
2627 minIndex = i;
2628 } else if (x > max) {
2629 max = x;
2630 maxIndex = i;
2631 }
2632 }
2633 *maxIndexPtr = maxIndex;
2634 return minIndex;
2635}
2636
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002637static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002638 if (dir) {
2639 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2640 }
reed@google.comac8543f2012-01-30 20:51:25 +00002641}
2642
reed@google.comc1ea60a2012-01-31 15:15:36 +00002643#if 0
2644#include "SkString.h"
2645#include "../utils/SkParsePath.h"
2646static void dumpPath(const SkPath& path) {
2647 SkString str;
2648 SkParsePath::ToSVGString(path, &str);
2649 SkDebugf("%s\n", str.c_str());
2650}
2651#endif
2652
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002653namespace {
2654// for use with convex_dir_test
2655double mul(double a, double b) { return a * b; }
2656SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2657double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2658SkScalar toScalar(SkScalar a) { return a; }
2659
2660// determines the winding direction of a convex polygon with the precision
2661// of T. CAST_SCALAR casts an SkScalar to T.
2662template <typename T, T (CAST_SCALAR)(SkScalar)>
2663bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2664 // we find the first three points that form a non-degenerate
2665 // triangle. If there are no such points then the path is
2666 // degenerate. The first is always point 0. Now we find the second
2667 // point.
2668 int i = 0;
2669 enum { kX = 0, kY = 1 };
2670 T v0[2];
2671 while (1) {
2672 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2673 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2674 if (v0[kX] || v0[kY]) {
2675 break;
2676 }
2677 if (++i == n - 1) {
2678 return false;
2679 }
2680 }
2681 // now find a third point that is not colinear with the first two
2682 // points and check the orientation of the triangle (which will be
2683 // the same as the orientation of the path).
2684 for (++i; i < n; ++i) {
2685 T v1[2];
2686 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2687 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2688 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2689 if (0 != cross) {
2690 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2691 return true;
2692 }
2693 }
2694 return false;
2695}
2696}
2697
reed@google.comac8543f2012-01-30 20:51:25 +00002698/*
2699 * We loop through all contours, and keep the computed cross-product of the
2700 * contour that contained the global y-max. If we just look at the first
2701 * contour, we may find one that is wound the opposite way (correctly) since
2702 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2703 * that is outer most (or at least has the global y-max) before we can consider
2704 * its cross product.
2705 */
reed@google.com69a99432012-01-10 18:00:10 +00002706bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002707// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002708 // don't want to pay the cost for computing this if it
2709 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002710
2711 if (kUnknown_Direction != fDirection) {
2712 *dir = static_cast<Direction>(fDirection);
2713 return true;
2714 }
reed@google.com69a99432012-01-10 18:00:10 +00002715 const Convexity conv = this->getConvexityOrUnknown();
2716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002717 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002718
reed@google.comac8543f2012-01-30 20:51:25 +00002719 // initialize with our logical y-min
2720 SkScalar ymax = this->getBounds().fTop;
2721 SkScalar ymaxCross = 0;
2722
reed@google.com69a99432012-01-10 18:00:10 +00002723 for (; !iter.done(); iter.next()) {
2724 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002725 if (n < 3) {
2726 continue;
2727 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002728
reed@google.comcabaf1d2012-01-11 21:03:05 +00002729 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002730 SkScalar cross = 0;
2731 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002732 // We try first at scalar precision, and then again at double
2733 // precision. This is because the vectors computed between distant
2734 // points may lose too much precision.
2735 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002736 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002737 return true;
2738 }
2739 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002740 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002741 return true;
2742 } else {
2743 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002744 }
2745 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002746 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002747 if (pts[index].fY < ymax) {
2748 continue;
2749 }
2750
reed@google.comc1ea60a2012-01-31 15:15:36 +00002751 // If there is more than 1 distinct point at the y-max, we take the
2752 // x-min and x-max of them and just subtract to compute the dir.
2753 if (pts[(index + 1) % n].fY == pts[index].fY) {
2754 int maxIndex;
2755 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002756 if (minIndex == maxIndex) {
2757 goto TRY_CROSSPROD;
2758 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002759 SkASSERT(pts[minIndex].fY == pts[index].fY);
2760 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2761 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2762 // we just subtract the indices, and let that auto-convert to
2763 // SkScalar, since we just want - or + to signal the direction.
2764 cross = minIndex - maxIndex;
2765 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002766 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002767 // Find a next and prev index to use for the cross-product test,
2768 // but we try to find pts that form non-zero vectors from pts[index]
2769 //
2770 // Its possible that we can't find two non-degenerate vectors, so
2771 // we have to guard our search (e.g. all the pts could be in the
2772 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002773
reed@google.comc1ea60a2012-01-31 15:15:36 +00002774 // we pass n - 1 instead of -1 so we don't foul up % operator by
2775 // passing it a negative LH argument.
2776 int prev = find_diff_pt(pts, index, n, n - 1);
2777 if (prev == index) {
2778 // completely degenerate, skip to next contour
2779 continue;
2780 }
2781 int next = find_diff_pt(pts, index, n, 1);
2782 SkASSERT(next != index);
2783 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002784 // 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 +00002785 // x-direction. We really should continue to walk away from the degeneracy until
2786 // there is a divergence.
2787 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002788 // construct the subtract so we get the correct Direction below
2789 cross = pts[index].fX - pts[next].fX;
2790 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002791 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002792
reed@google.comac8543f2012-01-30 20:51:25 +00002793 if (cross) {
2794 // record our best guess so far
2795 ymax = pts[index].fY;
2796 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002797 }
reed@google.com69a99432012-01-10 18:00:10 +00002798 }
2799 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002800 if (ymaxCross) {
2801 crossToDir(ymaxCross, dir);
2802 fDirection = *dir;
2803 return true;
2804 } else {
2805 return false;
2806 }
reed@google.comac8543f2012-01-30 20:51:25 +00002807}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808
2809///////////////////////////////////////////////////////////////////////////////
2810
2811static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2812 SkScalar D, SkScalar t) {
2813 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2814}
2815
2816static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2817 SkScalar t) {
2818 SkScalar A = c3 + 3*(c1 - c2) - c0;
2819 SkScalar B = 3*(c2 - c1 - c1 + c0);
2820 SkScalar C = 3*(c1 - c0);
2821 SkScalar D = c0;
2822 return eval_cubic_coeff(A, B, C, D, t);
2823}
2824
2825/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2826 t value such that cubic(t) = target
2827 */
2828static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2829 SkScalar target, SkScalar* t) {
2830 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2831 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 SkScalar D = c0 - target;
2834 SkScalar A = c3 + 3*(c1 - c2) - c0;
2835 SkScalar B = 3*(c2 - c1 - c1 + c0);
2836 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002837
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2839 SkScalar minT = 0;
2840 SkScalar maxT = SK_Scalar1;
2841 SkScalar mid;
2842 int i;
2843 for (i = 0; i < 16; i++) {
2844 mid = SkScalarAve(minT, maxT);
2845 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2846 if (delta < 0) {
2847 minT = mid;
2848 delta = -delta;
2849 } else {
2850 maxT = mid;
2851 }
2852 if (delta < TOLERANCE) {
2853 break;
2854 }
2855 }
2856 *t = mid;
2857 return true;
2858}
2859
2860template <size_t N> static void find_minmax(const SkPoint pts[],
2861 SkScalar* minPtr, SkScalar* maxPtr) {
2862 SkScalar min, max;
2863 min = max = pts[0].fX;
2864 for (size_t i = 1; i < N; ++i) {
2865 min = SkMinScalar(min, pts[i].fX);
2866 max = SkMaxScalar(max, pts[i].fX);
2867 }
2868 *minPtr = min;
2869 *maxPtr = max;
2870}
2871
2872static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2873 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 int dir = 1;
2876 if (pts[0].fY > pts[3].fY) {
2877 storage[0] = pts[3];
2878 storage[1] = pts[2];
2879 storage[2] = pts[1];
2880 storage[3] = pts[0];
2881 pts = storage;
2882 dir = -1;
2883 }
2884 if (y < pts[0].fY || y >= pts[3].fY) {
2885 return 0;
2886 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 // quickreject or quickaccept
2889 SkScalar min, max;
2890 find_minmax<4>(pts, &min, &max);
2891 if (x < min) {
2892 return 0;
2893 }
2894 if (x > max) {
2895 return dir;
2896 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002897
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002898 // compute the actual x(t) value
2899 SkScalar t, xt;
2900 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2901 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2902 } else {
2903 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2904 xt = y < mid ? pts[0].fX : pts[3].fX;
2905 }
2906 return xt < x ? dir : 0;
2907}
2908
2909static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2910 SkPoint dst[10];
2911 int n = SkChopCubicAtYExtrema(pts, dst);
2912 int w = 0;
2913 for (int i = 0; i <= n; ++i) {
2914 w += winding_mono_cubic(&dst[i * 3], x, y);
2915 }
2916 return w;
2917}
2918
2919static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2920 SkScalar y0 = pts[0].fY;
2921 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002922
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 int dir = 1;
2924 if (y0 > y2) {
2925 SkTSwap(y0, y2);
2926 dir = -1;
2927 }
2928 if (y < y0 || y >= y2) {
2929 return 0;
2930 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002931
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002932 // bounds check on X (not required. is it faster?)
2933#if 0
2934 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2935 return 0;
2936 }
2937#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002938
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 SkScalar roots[2];
2940 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2941 2 * (pts[1].fY - pts[0].fY),
2942 pts[0].fY - y,
2943 roots);
2944 SkASSERT(n <= 1);
2945 SkScalar xt;
2946 if (0 == n) {
2947 SkScalar mid = SkScalarAve(y0, y2);
2948 // Need [0] and [2] if dir == 1
2949 // and [2] and [0] if dir == -1
2950 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2951 } else {
2952 SkScalar t = roots[0];
2953 SkScalar C = pts[0].fX;
2954 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2955 SkScalar B = 2 * (pts[1].fX - C);
2956 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2957 }
2958 return xt < x ? dir : 0;
2959}
2960
2961static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2962 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2963 if (y0 == y1) {
2964 return true;
2965 }
2966 if (y0 < y1) {
2967 return y1 <= y2;
2968 } else {
2969 return y1 >= y2;
2970 }
2971}
2972
2973static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2974 SkPoint dst[5];
2975 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2978 n = SkChopQuadAtYExtrema(pts, dst);
2979 pts = dst;
2980 }
2981 int w = winding_mono_quad(pts, x, y);
2982 if (n > 0) {
2983 w += winding_mono_quad(&pts[2], x, y);
2984 }
2985 return w;
2986}
2987
2988static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2989 SkScalar x0 = pts[0].fX;
2990 SkScalar y0 = pts[0].fY;
2991 SkScalar x1 = pts[1].fX;
2992 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002993
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002994 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002995
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 int dir = 1;
2997 if (y0 > y1) {
2998 SkTSwap(y0, y1);
2999 dir = -1;
3000 }
3001 if (y < y0 || y >= y1) {
3002 return 0;
3003 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
3006 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003007
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 if (SkScalarSignAsInt(cross) == dir) {
3009 dir = 0;
3010 }
3011 return dir;
3012}
3013
3014bool SkPath::contains(SkScalar x, SkScalar y) const {
3015 bool isInverse = this->isInverseFillType();
3016 if (this->isEmpty()) {
3017 return isInverse;
3018 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003019
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 const SkRect& bounds = this->getBounds();
3021 if (!bounds.contains(x, y)) {
3022 return isInverse;
3023 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003024
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 SkPath::Iter iter(*this, true);
3026 bool done = false;
3027 int w = 0;
3028 do {
3029 SkPoint pts[4];
3030 switch (iter.next(pts, false)) {
3031 case SkPath::kMove_Verb:
3032 case SkPath::kClose_Verb:
3033 break;
3034 case SkPath::kLine_Verb:
3035 w += winding_line(pts, x, y);
3036 break;
3037 case SkPath::kQuad_Verb:
3038 w += winding_quad(pts, x, y);
3039 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003040 case SkPath::kConic_Verb:
3041 SkASSERT(0);
3042 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003043 case SkPath::kCubic_Verb:
3044 w += winding_cubic(pts, x, y);
3045 break;
3046 case SkPath::kDone_Verb:
3047 done = true;
3048 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003049 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003050 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003051
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003052 switch (this->getFillType()) {
3053 case SkPath::kEvenOdd_FillType:
3054 case SkPath::kInverseEvenOdd_FillType:
3055 w &= 1;
3056 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003057 default:
3058 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003059 }
3060 return SkToBool(w);
3061}