blob: ed2f555ca56bfcca932c7be2a33621faffa35748 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
80 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
85 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000102 fPath->fBounds = fRect;
103 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000104 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000106 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000107 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000108 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 }
110 }
reed@google.comabf15c12011-01-18 20:35:51 +0000111
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000113 SkPath* fPath;
114 SkRect fRect;
115 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000116 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000117 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000120 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000122 // Mark the path's bounds as dirty if (1) they are, or (2) the path
123 // is non-finite, and therefore its bounds are not meaningful
124 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000127 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000128 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 }
130};
131
reed@google.com0bb18bb2012-07-26 15:20:36 +0000132// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000133static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
134 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000135 if (count <= 1) { // we ignore just 1 point (moveto)
136 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000137 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000139 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 }
141}
142
143////////////////////////////////////////////////////////////////////////////
144
145/*
146 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000147 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
149
150 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000151 1. If we encounter degenerate segments, remove them
152 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
153 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
154 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155*/
156
157////////////////////////////////////////////////////////////////////////////
158
reed@google.comd335d1d2012-01-12 18:17:11 +0000159// flag to require a moveTo if we begin with something else, like lineTo etc.
160#define INITIAL_LASTMOVETOINDEX_VALUE ~0
161
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000162SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000163 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164#ifdef SK_BUILD_FOR_ANDROID
165 , fGenerationID(0)
166#endif
167{
168 this->resetFields();
169}
170
171void SkPath::resetFields() {
172 //fPathRef is assumed to have been emptied by the caller.
173 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
174 fFillType = kWinding_FillType;
175 fSegmentMask = 0;
176 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000177 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000178 fDirection = kUnknown_Direction;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fIsFinite = false;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000180 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000181#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 GEN_ID_INC;
djsollen@google.come63793a2012-03-21 15:39:03 +0000183 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000184#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000188 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000189 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000190#ifdef SK_BUILD_FOR_ANDROID
191 fGenerationID = that.fGenerationID;
192 fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
193#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195}
196
197SkPath::~SkPath() {
198 SkDEBUGCODE(this->validate();)
199}
200
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201SkPath& SkPath::operator=(const SkPath& that) {
202 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 if (this != &that) {
205 fPathRef.reset(SkRef(that.fPathRef.get()));
206 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000207#ifdef SK_BUILD_FOR_ANDROID
208 GEN_ID_INC; // Similar to swap, we can't just copy this or it could go back in time.
209 fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
210#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211 }
212 SkDEBUGCODE(this->validate();)
213 return *this;
214}
215
bungeman@google.coma5809a32013-06-21 15:13:34 +0000216void SkPath::copyFields(const SkPath& that) {
217 //fPathRef is assumed to have been set by the caller.
218 fBounds = that.fBounds;
219 fLastMoveToIndex = that.fLastMoveToIndex;
220 fFillType = that.fFillType;
221 fSegmentMask = that.fSegmentMask;
222 fBoundsIsDirty = that.fBoundsIsDirty;
223 fConvexity = that.fConvexity;
224 fDirection = that.fDirection;
225 fIsFinite = that.fIsFinite;
226 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000227}
228
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000229bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000230 // note: don't need to look at isConvex or bounds, since just comparing the
231 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000232
233 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
234 // since it is only a cache of info in the fVerbs, but its a fast way to
235 // notice a difference
236
reed@android.com3abec1d2009-03-02 05:36:20 +0000237 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000238 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000239 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000240}
241
bungeman@google.coma5809a32013-06-21 15:13:34 +0000242void SkPath::swap(SkPath& that) {
243 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
bungeman@google.coma5809a32013-06-21 15:13:34 +0000245 if (this != &that) {
246 fPathRef.swap(&that.fPathRef);
247 SkTSwap<SkRect>(fBounds, that.fBounds);
248 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
249 SkTSwap<uint8_t>(fFillType, that.fFillType);
250 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
251 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
252 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
253 SkTSwap<uint8_t>(fDirection, that.fDirection);
254 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
255 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000256#ifdef SK_BUILD_FOR_ANDROID
257 // It doesn't really make sense to swap the generation IDs here, because they might go
258 // backwards. To be safe we increment both to mark them both as changed.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000259 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000260 GEN_ID_PTR_INC(&that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000261 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
262#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 }
264}
265
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266static inline bool check_edge_against_rect(const SkPoint& p0,
267 const SkPoint& p1,
268 const SkRect& rect,
269 SkPath::Direction dir) {
270 const SkPoint* edgeBegin;
271 SkVector v;
272 if (SkPath::kCW_Direction == dir) {
273 v = p1 - p0;
274 edgeBegin = &p0;
275 } else {
276 v = p0 - p1;
277 edgeBegin = &p1;
278 }
279 if (v.fX || v.fY) {
280 // check the cross product of v with the vec from edgeBegin to each rect corner
281 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
282 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
283 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
284 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
285 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
286 return false;
287 }
288 }
289 return true;
290}
291
292bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
293 // This only handles non-degenerate convex paths currently.
294 if (kConvex_Convexity != this->getConvexity()) {
295 return false;
296 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000297
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 Direction direction;
299 if (!this->cheapComputeDirection(&direction)) {
300 return false;
301 }
302
303 SkPoint firstPt;
304 SkPoint prevPt;
305 RawIter iter(*this);
306 SkPath::Verb verb;
307 SkPoint pts[4];
308 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000309 SkDEBUGCODE(int segmentCount = 0;)
310 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311
312 while ((verb = iter.next(pts)) != kDone_Verb) {
313 int nextPt = -1;
314 switch (verb) {
315 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000316 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000317 SkDEBUGCODE(++moveCnt);
318 firstPt = prevPt = pts[0];
319 break;
320 case kLine_Verb:
321 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000322 SkASSERT(moveCnt && !closeCount);
323 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000324 break;
325 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000326 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000327 SkASSERT(moveCnt && !closeCount);
328 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000329 nextPt = 2;
330 break;
331 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000332 SkASSERT(moveCnt && !closeCount);
333 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000334 nextPt = 3;
335 break;
336 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000337 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000338 break;
339 default:
340 SkDEBUGFAIL("unknown verb");
341 }
342 if (-1 != nextPt) {
343 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
344 return false;
345 }
346 prevPt = pts[nextPt];
347 }
348 }
349
350 return check_edge_against_rect(prevPt, firstPt, rect, direction);
351}
352
djsollen@google.com56c69772011-11-08 19:00:26 +0000353#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000354uint32_t SkPath::getGenerationID() const {
355 return fGenerationID;
356}
djsollen@google.come63793a2012-03-21 15:39:03 +0000357
358const SkPath* SkPath::getSourcePath() const {
359 return fSourcePath;
360}
361
362void SkPath::setSourcePath(const SkPath* path) {
363 fSourcePath = path;
364}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000365#endif
366
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367void SkPath::reset() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
374void SkPath::rewind() {
375 SkDEBUGCODE(this->validate();)
376
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000377 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000378 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
381bool SkPath::isEmpty() const {
382 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384}
385
reed@google.com7e6c4d12012-05-10 14:05:43 +0000386bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000387 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000388
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000389 if (2 == verbCount) {
390 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
391 if (kLine_Verb == fPathRef->atVerb(1)) {
392 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000393 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000394 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000395 line[0] = pts[0];
396 line[1] = pts[1];
397 }
398 return true;
399 }
400 }
401 return false;
402}
403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404/*
405 Determines if path is a rect by keeping track of changes in direction
406 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000407
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 The direction is computed such that:
409 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000410 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000412 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000413
caryclark@google.comf1316942011-07-26 19:54:45 +0000414A rectangle cycles up/right/down/left or up/left/down/right.
415
416The test fails if:
417 The path is closed, and followed by a line.
418 A second move creates a new endpoint.
419 A diagonal line is parsed.
420 There's more than four changes of direction.
421 There's a discontinuity on the line (e.g., a move in the middle)
422 The line reverses direction.
423 The rectangle doesn't complete a cycle.
424 The path contains a quadratic or cubic.
425 The path contains fewer than four points.
426 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428It's OK if the path has:
429 Several colinear line segments composing a rectangle side.
430 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000431
caryclark@google.comf1316942011-07-26 19:54:45 +0000432The direction takes advantage of the corners found since opposite sides
433must travel in opposite directions.
434
435FIXME: Allow colinear quads and cubics to be treated like lines.
436FIXME: If the API passes fill-only, return true if the filled stroke
437 is a rectangle, though the caller failed to close the path.
438 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000439bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
440 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 int corners = 0;
442 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000444 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000445 first.set(0, 0);
446 last.set(0, 0);
447 int firstDirection = 0;
448 int lastDirection = 0;
449 int nextDirection = 0;
450 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000452 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000453 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
454 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000456 savePts = pts;
457 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 autoClose = true;
459 case kLine_Verb: {
460 SkScalar left = last.fX;
461 SkScalar top = last.fY;
462 SkScalar right = pts->fX;
463 SkScalar bottom = pts->fY;
464 ++pts;
465 if (left != right && top != bottom) {
466 return false; // diagonal
467 }
468 if (left == right && top == bottom) {
469 break; // single point on side OK
470 }
471 nextDirection = (left != right) << 0 |
472 (left < right || top < bottom) << 1;
473 if (0 == corners) {
474 firstDirection = nextDirection;
475 first = last;
476 last = pts[-1];
477 corners = 1;
478 closedOrMoved = false;
479 break;
480 }
481 if (closedOrMoved) {
482 return false; // closed followed by a line
483 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000484 if (autoClose && nextDirection == firstDirection) {
485 break; // colinear with first
486 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 closedOrMoved = autoClose;
488 if (lastDirection != nextDirection) {
489 if (++corners > 4) {
490 return false; // too many direction changes
491 }
492 }
493 last = pts[-1];
494 if (lastDirection == nextDirection) {
495 break; // colinear segment
496 }
497 // Possible values for corners are 2, 3, and 4.
498 // When corners == 3, nextDirection opposes firstDirection.
499 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000500 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000501 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
502 if ((directionCycle ^ turn) != nextDirection) {
503 return false; // direction didn't follow cycle
504 }
505 break;
506 }
507 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000508 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 case kCubic_Verb:
510 return false; // quadratic, cubic not allowed
511 case kMove_Verb:
512 last = *pts++;
513 closedOrMoved = true;
514 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000515 default:
516 SkASSERT(!"unexpected verb");
517 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000519 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000520 lastDirection = nextDirection;
521 }
522 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000523 bool result = 4 == corners && (first == last || autoClose);
524 if (savePts) {
525 *ptsPtr = savePts;
526 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000527 if (result && isClosed) {
528 *isClosed = autoClose;
529 }
530 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000531 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000532 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 return result;
534}
535
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000536bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000537 SkDEBUGCODE(this->validate();)
538 int currVerb = 0;
539 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000540 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
541 if (result && rect) {
542 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000543 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000544 return result;
545}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000546
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000547bool SkPath::isRect(bool* isClosed, Direction* direction) const {
548 SkDEBUGCODE(this->validate();)
549 int currVerb = 0;
550 const SkPoint* pts = fPathRef->points();
551 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000552}
553
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000555 SkDEBUGCODE(this->validate();)
556 int currVerb = 0;
557 const SkPoint* pts = fPathRef->points();
558 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000559 Direction testDirs[2];
560 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return false;
562 }
563 const SkPoint* last = pts;
564 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000565 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000566 testRects[0].set(first, SkToS32(last - first));
567 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 if (testRects[0].contains(testRects[1])) {
569 if (rects) {
570 rects[0] = testRects[0];
571 rects[1] = testRects[1];
572 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000573 if (dirs) {
574 dirs[0] = testDirs[0];
575 dirs[1] = testDirs[1];
576 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000577 return true;
578 }
579 if (testRects[1].contains(testRects[0])) {
580 if (rects) {
581 rects[0] = testRects[1];
582 rects[1] = testRects[0];
583 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000584 if (dirs) {
585 dirs[0] = testDirs[1];
586 dirs[1] = testDirs[0];
587 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 return true;
589 }
590 }
591 return false;
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countPoints() const {
595 return fPathRef->countPoints();
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countPoints());
604 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
605 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606}
607
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000608SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000609 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
610 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000611 }
612 return SkPoint::Make(0, 0);
613}
614
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000615int SkPath::countVerbs() const {
616 return fPathRef->countVerbs();
617}
618
619static inline void copy_verbs_reverse(uint8_t* inorderDst,
620 const uint8_t* reversedSrc,
621 int count) {
622 for (int i = 0; i < count; ++i) {
623 inorderDst[i] = reversedSrc[~i];
624 }
625}
626
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000627int SkPath::getVerbs(uint8_t dst[], int max) const {
628 SkDEBUGCODE(this->validate();)
629
630 SkASSERT(max >= 0);
631 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 int count = SkMin32(max, fPathRef->countVerbs());
633 copy_verbs_reverse(dst, fPathRef->verbs(), count);
634 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000635}
636
reed@google.com294dd7b2011-10-11 11:58:32 +0000637bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000641 if (count > 0) {
642 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000645 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000647 if (lastPt) {
648 lastPt->set(0, 0);
649 }
650 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651}
652
653void SkPath::setLastPt(SkScalar x, SkScalar y) {
654 SkDEBUGCODE(this->validate();)
655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 if (count == 0) {
658 this->moveTo(x, y);
659 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000660 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor ed(&fPathRef);
662 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000663 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 }
665}
666
reed@android.comd252db02009-04-01 18:31:44 +0000667void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000669 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000672 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673}
674
reed@google.com04863fa2011-05-15 04:08:24 +0000675void SkPath::setConvexity(Convexity c) {
676 if (fConvexity != c) {
677 fConvexity = c;
678 GEN_ID_INC;
679 }
680}
681
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682//////////////////////////////////////////////////////////////////////////////
683// Construction methods
684
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000685#define DIRTY_AFTER_EDIT \
686 do { \
687 fBoundsIsDirty = true; \
688 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000689 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000690 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000691 } while (0)
692
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000693#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
694 do { \
695 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000696 } while (0)
697
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698void SkPath::incReserve(U16CPU inc) {
699 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000700 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701 SkDEBUGCODE(this->validate();)
702}
703
704void SkPath::moveTo(SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708
reed@google.comd335d1d2012-01-12 18:17:11 +0000709 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000711
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000714 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000715 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716}
717
718void SkPath::rMoveTo(SkScalar x, SkScalar y) {
719 SkPoint pt;
720 this->getLastPt(&pt);
721 this->moveTo(pt.fX + x, pt.fY + y);
722}
723
reed@google.comd335d1d2012-01-12 18:17:11 +0000724void SkPath::injectMoveToIfNeeded() {
725 if (fLastMoveToIndex < 0) {
726 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000728 x = y = 0;
729 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000730 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000731 x = pt.fX;
732 y = pt.fY;
733 }
734 this->moveTo(x, y);
735 }
736}
737
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738void SkPath::lineTo(SkScalar x, SkScalar y) {
739 SkDEBUGCODE(this->validate();)
740
reed@google.comd335d1d2012-01-12 18:17:11 +0000741 this->injectMoveToIfNeeded();
742
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000743 SkPathRef::Editor ed(&fPathRef);
744 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000745 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000747 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000748 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749}
750
751void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000752 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753 SkPoint pt;
754 this->getLastPt(&pt);
755 this->lineTo(pt.fX + x, pt.fY + y);
756}
757
758void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
759 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000760
reed@google.comd335d1d2012-01-12 18:17:11 +0000761 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000762
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000763 SkPathRef::Editor ed(&fPathRef);
764 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 pts[0].set(x1, y1);
766 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000767 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000768
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000769 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000770 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771}
772
773void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000774 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775 SkPoint pt;
776 this->getLastPt(&pt);
777 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
778}
779
reed@google.com277c3f82013-05-31 15:17:50 +0000780void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
781 SkScalar w) {
782 // check for <= 0 or NaN with this test
783 if (!(w > 0)) {
784 this->lineTo(x2, y2);
785 } else if (!SkScalarIsFinite(w)) {
786 this->lineTo(x1, y1);
787 this->lineTo(x2, y2);
788 } else if (SK_Scalar1 == w) {
789 this->quadTo(x1, y1, x2, y2);
790 } else {
791 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
reed@google.com277c3f82013-05-31 15:17:50 +0000793 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000794
reed@google.com277c3f82013-05-31 15:17:50 +0000795 SkPathRef::Editor ed(&fPathRef);
796 SkPoint* pts = ed.growForConic(w);
797 pts[0].set(x1, y1);
798 pts[1].set(x2, y2);
799 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000800
reed@google.com277c3f82013-05-31 15:17:50 +0000801 GEN_ID_INC;
802 DIRTY_AFTER_EDIT;
803 }
804}
805
806void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
807 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000808 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000809 SkPoint pt;
810 this->getLastPt(&pt);
811 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
812}
813
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
815 SkScalar x3, SkScalar y3) {
816 SkDEBUGCODE(this->validate();)
817
reed@google.comd335d1d2012-01-12 18:17:11 +0000818 this->injectMoveToIfNeeded();
819
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000820 SkPathRef::Editor ed(&fPathRef);
821 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 pts[0].set(x1, y1);
823 pts[1].set(x2, y2);
824 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000825 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000827 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000828 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829}
830
831void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
832 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000833 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 SkPoint pt;
835 this->getLastPt(&pt);
836 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
837 pt.fX + x3, pt.fY + y3);
838}
839
840void SkPath::close() {
841 SkDEBUGCODE(this->validate();)
842
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000843 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000845 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 case kLine_Verb:
847 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000848 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000850 case kMove_Verb: {
851 SkPathRef::Editor ed(&fPathRef);
852 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000853 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000855 }
reed@google.com277c3f82013-05-31 15:17:50 +0000856 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000857 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000858 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000859 default:
860 SkASSERT(!"unexpected verb");
861 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 }
863 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000864
865 // signal that we need a moveTo to follow us (unless we're done)
866#if 0
867 if (fLastMoveToIndex >= 0) {
868 fLastMoveToIndex = ~fLastMoveToIndex;
869 }
870#else
871 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
872#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873}
874
875///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000876
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000877static void assert_known_direction(int dir) {
878 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
879}
880
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881void SkPath::addRect(const SkRect& rect, Direction dir) {
882 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
883}
884
885void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
886 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000887 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000888 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
889 SkAutoDisableDirectionCheck addc(this);
890
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
892
893 this->incReserve(5);
894
895 this->moveTo(left, top);
896 if (dir == kCCW_Direction) {
897 this->lineTo(left, bottom);
898 this->lineTo(right, bottom);
899 this->lineTo(right, top);
900 } else {
901 this->lineTo(right, top);
902 this->lineTo(right, bottom);
903 this->lineTo(left, bottom);
904 }
905 this->close();
906}
907
reed@google.com744faba2012-05-29 19:54:52 +0000908void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
909 SkDEBUGCODE(this->validate();)
910 if (count <= 0) {
911 return;
912 }
913
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000914 SkPathRef::Editor ed(&fPathRef);
915 fLastMoveToIndex = ed.pathRef()->countPoints();
916 uint8_t* vb;
917 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000918 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000919 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000920
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000921 memcpy(p, pts, count * sizeof(SkPoint));
922 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000923 if (count > 1) {
924 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
925 // be 0, the compiler will remove the test/branch entirely.
926 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000927 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000928 } else {
929 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000930 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000931 }
932 }
933 fSegmentMask |= kLine_SegmentMask;
934 }
935 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000936 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000937 }
938
939 GEN_ID_INC;
940 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000941 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000942}
943
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944static void add_corner_arc(SkPath* path, const SkRect& rect,
945 SkScalar rx, SkScalar ry, int startAngle,
946 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000947 // These two asserts are not sufficient, since really we want to know
948 // that the pair of radii (e.g. left and right, or top and bottom) sum
949 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000950 // these conservative asserts.
951 SkASSERT(0 <= rx && rx <= rect.width());
952 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000953
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 SkRect r;
955 r.set(-rx, -ry, rx, ry);
956
957 switch (startAngle) {
958 case 0:
959 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
960 break;
961 case 90:
962 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
963 break;
964 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
965 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000966 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 }
reed@google.comabf15c12011-01-18 20:35:51 +0000968
reed@android.com8a1c16f2008-12-17 15:59:43 +0000969 SkScalar start = SkIntToScalar(startAngle);
970 SkScalar sweep = SkIntToScalar(90);
971 if (SkPath::kCCW_Direction == dir) {
972 start += sweep;
973 sweep = -sweep;
974 }
reed@google.comabf15c12011-01-18 20:35:51 +0000975
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 path->arcTo(r, start, sweep, forceMoveTo);
977}
978
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000979void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000981 SkRRect rrect;
982 rrect.setRectRadii(rect, (const SkVector*) radii);
983 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984}
985
reed@google.com4ed0fb72012-12-12 20:48:18 +0000986void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000987 assert_known_direction(dir);
988
989 if (rrect.isEmpty()) {
990 return;
991 }
992
reed@google.com4ed0fb72012-12-12 20:48:18 +0000993 const SkRect& bounds = rrect.getBounds();
994
995 if (rrect.isRect()) {
996 this->addRect(bounds, dir);
997 } else if (rrect.isOval()) {
998 this->addOval(bounds, dir);
999 } else if (rrect.isSimple()) {
1000 const SkVector& rad = rrect.getSimpleRadii();
1001 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1002 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001003 SkAutoPathBoundsUpdate apbu(this, bounds);
1004
1005 if (kCW_Direction == dir) {
1006 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1007 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1008 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1009 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1010 } else {
1011 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1012 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1013 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1014 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1015 }
1016 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001017 }
1018}
1019
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001020bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001021 int count = fPathRef->countVerbs();
1022 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1023 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001024 if (*verbs == kLine_Verb ||
1025 *verbs == kQuad_Verb ||
1026 *verbs == kCubic_Verb) {
1027 return false;
1028 }
1029 ++verbs;
1030 }
1031 return true;
1032}
1033
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001034#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1035
1036void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1037 Direction dir) {
1038 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001039
humper@google.com75e3ca12013-04-08 21:44:11 +00001040 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001041 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001042 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001043 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001044 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1045 return;
1046 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001047
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001048 SkScalar w = rect.width();
1049 SkScalar halfW = SkScalarHalf(w);
1050 SkScalar h = rect.height();
1051 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001052
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001053 if (halfW <= 0 || halfH <= 0) {
1054 return;
1055 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001056
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001057 bool skip_hori = rx >= halfW;
1058 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001059
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001060 if (skip_hori && skip_vert) {
1061 this->addOval(rect, dir);
1062 return;
1063 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001064
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001065 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001066
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001067 SkAutoPathBoundsUpdate apbu(this, rect);
1068 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001069
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001070 if (skip_hori) {
1071 rx = halfW;
1072 } else if (skip_vert) {
1073 ry = halfH;
1074 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001075
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001076 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1077 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001078
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001079 this->incReserve(17);
1080 this->moveTo(rect.fRight - rx, rect.fTop);
1081 if (dir == kCCW_Direction) {
1082 if (!skip_hori) {
1083 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1084 }
1085 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1086 rect.fLeft, rect.fTop + ry - sy,
1087 rect.fLeft, rect.fTop + ry); // top-left
1088 if (!skip_vert) {
1089 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1090 }
1091 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1092 rect.fLeft + rx - sx, rect.fBottom,
1093 rect.fLeft + rx, rect.fBottom); // bot-left
1094 if (!skip_hori) {
1095 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1096 }
1097 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1098 rect.fRight, rect.fBottom - ry + sy,
1099 rect.fRight, rect.fBottom - ry); // bot-right
1100 if (!skip_vert) {
1101 this->lineTo(rect.fRight, rect.fTop + ry);
1102 }
1103 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1104 rect.fRight - rx + sx, rect.fTop,
1105 rect.fRight - rx, rect.fTop); // top-right
1106 } else {
1107 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1108 rect.fRight, rect.fTop + ry - sy,
1109 rect.fRight, rect.fTop + ry); // top-right
1110 if (!skip_vert) {
1111 this->lineTo(rect.fRight, rect.fBottom - ry);
1112 }
1113 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1114 rect.fRight - rx + sx, rect.fBottom,
1115 rect.fRight - rx, rect.fBottom); // bot-right
1116 if (!skip_hori) {
1117 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1118 }
1119 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1120 rect.fLeft, rect.fBottom - ry + sy,
1121 rect.fLeft, rect.fBottom - ry); // bot-left
1122 if (!skip_vert) {
1123 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1124 }
1125 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1126 rect.fLeft + rx - sx, rect.fTop,
1127 rect.fLeft + rx, rect.fTop); // top-left
1128 if (!skip_hori) {
1129 this->lineTo(rect.fRight - rx, rect.fTop); // top
1130 }
1131 }
1132 this->close();
1133}
1134
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001136 assert_known_direction(dir);
1137
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001138 /* If addOval() is called after previous moveTo(),
1139 this path is still marked as an oval. This is used to
1140 fit into WebKit's calling sequences.
1141 We can't simply check isEmpty() in this case, as additional
1142 moveTo() would mark the path non empty.
1143 */
1144 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001145 if (fIsOval) {
1146 fDirection = dir;
1147 } else {
1148 fDirection = kUnknown_Direction;
1149 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001150
1151 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001152 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001153
reed@android.com8a1c16f2008-12-17 15:59:43 +00001154 SkAutoPathBoundsUpdate apbu(this, oval);
1155
1156 SkScalar cx = oval.centerX();
1157 SkScalar cy = oval.centerY();
1158 SkScalar rx = SkScalarHalf(oval.width());
1159 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1162 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1163 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1164 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1165
1166 /*
1167 To handle imprecision in computing the center and radii, we revert to
1168 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1169 to ensure that we don't exceed the oval's bounds *ever*, since we want
1170 to use oval for our fast-bounds, rather than have to recompute it.
1171 */
1172 const SkScalar L = oval.fLeft; // cx - rx
1173 const SkScalar T = oval.fTop; // cy - ry
1174 const SkScalar R = oval.fRight; // cx + rx
1175 const SkScalar B = oval.fBottom; // cy + ry
1176
1177 this->incReserve(17); // 8 quads + close
1178 this->moveTo(R, cy);
1179 if (dir == kCCW_Direction) {
1180 this->quadTo( R, cy - sy, cx + mx, cy - my);
1181 this->quadTo(cx + sx, T, cx , T);
1182 this->quadTo(cx - sx, T, cx - mx, cy - my);
1183 this->quadTo( L, cy - sy, L, cy );
1184 this->quadTo( L, cy + sy, cx - mx, cy + my);
1185 this->quadTo(cx - sx, B, cx , B);
1186 this->quadTo(cx + sx, B, cx + mx, cy + my);
1187 this->quadTo( R, cy + sy, R, cy );
1188 } else {
1189 this->quadTo( R, cy + sy, cx + mx, cy + my);
1190 this->quadTo(cx + sx, B, cx , B);
1191 this->quadTo(cx - sx, B, cx - mx, cy + my);
1192 this->quadTo( L, cy + sy, L, cy );
1193 this->quadTo( L, cy - sy, cx - mx, cy - my);
1194 this->quadTo(cx - sx, T, cx , T);
1195 this->quadTo(cx + sx, T, cx + mx, cy - my);
1196 this->quadTo( R, cy - sy, R, cy );
1197 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 this->close();
1199}
1200
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001201bool SkPath::isOval(SkRect* rect) const {
1202 if (fIsOval && rect) {
1203 *rect = getBounds();
1204 }
1205
1206 return fIsOval;
1207}
1208
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1210 if (r > 0) {
1211 SkRect rect;
1212 rect.set(x - r, y - r, x + r, y + r);
1213 this->addOval(rect, dir);
1214 }
1215}
1216
1217#include "SkGeometry.h"
1218
1219static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1220 SkScalar sweepAngle,
1221 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001222
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001223 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001224 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1225 // Chrome uses this path to move into and out of ovals. If not
1226 // treated as a special case the moves can distort the oval's
1227 // bounding box (and break the circle special case).
1228 pts[0].set(oval.fRight, oval.centerY());
1229 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001230 } else if (0 == oval.width() && 0 == oval.height()) {
1231 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001232 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001233 // a rect.
1234 // TODO: optimizing the case where only one of width or height is zero
1235 // should also be considered. This case, however, doesn't seem to be
1236 // as common as the single point case.
1237 pts[0].set(oval.fRight, oval.fTop);
1238 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001239 }
1240
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 SkVector start, stop;
1242
1243 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1244 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1245 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001246
1247 /* If the sweep angle is nearly (but less than) 360, then due to precision
1248 loss in radians-conversion and/or sin/cos, we may end up with coincident
1249 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1250 of drawing a nearly complete circle (good).
1251 e.g. canvas.drawArc(0, 359.99, ...)
1252 -vs- canvas.drawArc(0, 359.9, ...)
1253 We try to detect this edge case, and tweak the stop vector
1254 */
1255 if (start == stop) {
1256 SkScalar sw = SkScalarAbs(sweepAngle);
1257 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1258 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1259 // make a guess at a tiny angle (in radians) to tweak by
1260 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1261 // not sure how much will be enough, so we use a loop
1262 do {
1263 stopRad -= deltaRad;
1264 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1265 } while (start == stop);
1266 }
1267 }
1268
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001270
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1272 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001273
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274 return SkBuildQuadArc(start, stop,
1275 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1276 &matrix, pts);
1277}
1278
1279void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1280 bool forceMoveTo) {
1281 if (oval.width() < 0 || oval.height() < 0) {
1282 return;
1283 }
1284
1285 SkPoint pts[kSkBuildQuadArcStorage];
1286 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1287 SkASSERT((count & 1) == 1);
1288
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001289 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290 forceMoveTo = true;
1291 }
1292 this->incReserve(count);
1293 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1294 for (int i = 1; i < count; i += 2) {
1295 this->quadTo(pts[i], pts[i+1]);
1296 }
1297}
1298
1299void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1300 SkScalar sweepAngle) {
1301 if (oval.isEmpty() || 0 == sweepAngle) {
1302 return;
1303 }
1304
1305 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1306
1307 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1308 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1309 return;
1310 }
1311
1312 SkPoint pts[kSkBuildQuadArcStorage];
1313 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1314
1315 this->incReserve(count);
1316 this->moveTo(pts[0]);
1317 for (int i = 1; i < count; i += 2) {
1318 this->quadTo(pts[i], pts[i+1]);
1319 }
1320}
1321
1322/*
1323 Need to handle the case when the angle is sharp, and our computed end-points
1324 for the arc go behind pt1 and/or p2...
1325*/
1326void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1327 SkScalar radius) {
1328 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001329
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 // need to know our prev pt so we can construct tangent vectors
1331 {
1332 SkPoint start;
1333 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001334 // Handle degenerate cases by adding a line to the first point and
1335 // bailing out.
1336 if ((x1 == start.fX && y1 == start.fY) ||
1337 (x1 == x2 && y1 == y2) ||
1338 radius == 0) {
1339 this->lineTo(x1, y1);
1340 return;
1341 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 before.setNormalize(x1 - start.fX, y1 - start.fY);
1343 after.setNormalize(x2 - x1, y2 - y1);
1344 }
reed@google.comabf15c12011-01-18 20:35:51 +00001345
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 SkScalar cosh = SkPoint::DotProduct(before, after);
1347 SkScalar sinh = SkPoint::CrossProduct(before, after);
1348
1349 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001350 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 return;
1352 }
reed@google.comabf15c12011-01-18 20:35:51 +00001353
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1355 if (dist < 0) {
1356 dist = -dist;
1357 }
1358
1359 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1360 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1361 SkRotationDirection arcDir;
1362
1363 // now turn before/after into normals
1364 if (sinh > 0) {
1365 before.rotateCCW();
1366 after.rotateCCW();
1367 arcDir = kCW_SkRotationDirection;
1368 } else {
1369 before.rotateCW();
1370 after.rotateCW();
1371 arcDir = kCCW_SkRotationDirection;
1372 }
1373
1374 SkMatrix matrix;
1375 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001376
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 matrix.setScale(radius, radius);
1378 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1379 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001380
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001382
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 this->incReserve(count);
1384 // [xx,yy] == pts[0]
1385 this->lineTo(xx, yy);
1386 for (int i = 1; i < count; i += 2) {
1387 this->quadTo(pts[i], pts[i+1]);
1388 }
1389}
1390
1391///////////////////////////////////////////////////////////////////////////////
1392
1393void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1394 SkMatrix matrix;
1395
1396 matrix.setTranslate(dx, dy);
1397 this->addPath(path, matrix);
1398}
1399
1400void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001401 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001403 fIsOval = false;
1404
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001405 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406 SkPoint pts[4];
1407 Verb verb;
1408
1409 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1410
1411 while ((verb = iter.next(pts)) != kDone_Verb) {
1412 switch (verb) {
1413 case kMove_Verb:
1414 proc(matrix, &pts[0], &pts[0], 1);
1415 this->moveTo(pts[0]);
1416 break;
1417 case kLine_Verb:
1418 proc(matrix, &pts[1], &pts[1], 1);
1419 this->lineTo(pts[1]);
1420 break;
1421 case kQuad_Verb:
1422 proc(matrix, &pts[1], &pts[1], 2);
1423 this->quadTo(pts[1], pts[2]);
1424 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001425 case kConic_Verb:
1426 proc(matrix, &pts[1], &pts[1], 2);
1427 this->conicTo(pts[1], pts[2], iter.conicWeight());
1428 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 case kCubic_Verb:
1430 proc(matrix, &pts[1], &pts[1], 3);
1431 this->cubicTo(pts[1], pts[2], pts[3]);
1432 break;
1433 case kClose_Verb:
1434 this->close();
1435 break;
1436 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001437 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 }
1439 }
1440}
1441
1442///////////////////////////////////////////////////////////////////////////////
1443
reed@google.com277c3f82013-05-31 15:17:50 +00001444static int pts_in_verb(unsigned verb) {
1445 static const uint8_t gPtsInVerb[] = {
1446 1, // kMove
1447 1, // kLine
1448 2, // kQuad
1449 2, // kConic
1450 3, // kCubic
1451 0, // kClose
1452 0 // kDone
1453 };
1454
1455 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1456 return gPtsInVerb[verb];
1457}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458
1459// ignore the initial moveto, and stop when the 1st contour ends
1460void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001461 int i, vcount = path.fPathRef->countVerbs();
1462 // exit early if the path is empty, or just has a moveTo.
1463 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 return;
1465 }
1466
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001467 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001469 fIsOval = false;
1470
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001471 const uint8_t* verbs = path.fPathRef->verbs();
1472 // skip the initial moveTo
1473 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001474 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001478 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 case kLine_Verb:
1480 this->lineTo(pts[0].fX, pts[0].fY);
1481 break;
1482 case kQuad_Verb:
1483 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1484 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001485 case kConic_Verb:
1486 this->conicTo(pts[0], pts[1], *conicWeight++);
1487 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 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 +00001490 break;
1491 case kClose_Verb:
1492 return;
1493 }
reed@google.com277c3f82013-05-31 15:17:50 +00001494 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 }
1496}
1497
1498// ignore the last point of the 1st contour
1499void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001500 int i, vcount = path.fPathRef->countVerbs();
1501 // exit early if the path is empty, or just has a moveTo.
1502 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 return;
1504 }
1505
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001508 fIsOval = false;
1509
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001510 const uint8_t* verbs = path.fPathRef->verbs();
1511 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001512 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 SkASSERT(verbs[~0] == kMove_Verb);
1515 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001516 unsigned v = verbs[~i];
1517 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 if (n == 0) {
1519 break;
1520 }
1521 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001522 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 }
1524
1525 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 case kLine_Verb:
1528 this->lineTo(pts[-1].fX, pts[-1].fY);
1529 break;
1530 case kQuad_Verb:
1531 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1532 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001533 case kConic_Verb:
1534 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1535 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 case kCubic_Verb:
1537 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1538 pts[-3].fX, pts[-3].fY);
1539 break;
1540 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001541 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 break;
1543 }
reed@google.com277c3f82013-05-31 15:17:50 +00001544 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 }
1546}
1547
reed@google.com63d73742012-01-10 15:33:12 +00001548void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001549 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001550
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001551 const SkPoint* pts = src.fPathRef->pointsEnd();
1552 // we will iterator through src's verbs backwards
1553 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1554 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001555 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001556
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001557 fIsOval = false;
1558
reed@google.com63d73742012-01-10 15:33:12 +00001559 bool needMove = true;
1560 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001561 while (verbs < verbsEnd) {
1562 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001563 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001564
1565 if (needMove) {
1566 --pts;
1567 this->moveTo(pts->fX, pts->fY);
1568 needMove = false;
1569 }
1570 pts -= n;
1571 switch (v) {
1572 case kMove_Verb:
1573 if (needClose) {
1574 this->close();
1575 needClose = false;
1576 }
1577 needMove = true;
1578 pts += 1; // so we see the point in "if (needMove)" above
1579 break;
1580 case kLine_Verb:
1581 this->lineTo(pts[0]);
1582 break;
1583 case kQuad_Verb:
1584 this->quadTo(pts[1], pts[0]);
1585 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001586 case kConic_Verb:
1587 this->conicTo(pts[1], pts[0], *--conicWeights);
1588 break;
reed@google.com63d73742012-01-10 15:33:12 +00001589 case kCubic_Verb:
1590 this->cubicTo(pts[2], pts[1], pts[0]);
1591 break;
1592 case kClose_Verb:
1593 needClose = true;
1594 break;
1595 default:
1596 SkASSERT(!"unexpected verb");
1597 }
1598 }
1599}
1600
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601///////////////////////////////////////////////////////////////////////////////
1602
1603void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1604 SkMatrix matrix;
1605
1606 matrix.setTranslate(dx, dy);
1607 this->transform(matrix, dst);
1608}
1609
1610#include "SkGeometry.h"
1611
1612static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1613 int level = 2) {
1614 if (--level >= 0) {
1615 SkPoint tmp[5];
1616
1617 SkChopQuadAtHalf(pts, tmp);
1618 subdivide_quad_to(path, &tmp[0], level);
1619 subdivide_quad_to(path, &tmp[2], level);
1620 } else {
1621 path->quadTo(pts[1], pts[2]);
1622 }
1623}
1624
1625static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1626 int level = 2) {
1627 if (--level >= 0) {
1628 SkPoint tmp[7];
1629
1630 SkChopCubicAtHalf(pts, tmp);
1631 subdivide_cubic_to(path, &tmp[0], level);
1632 subdivide_cubic_to(path, &tmp[3], level);
1633 } else {
1634 path->cubicTo(pts[1], pts[2], pts[3]);
1635 }
1636}
1637
1638void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1639 SkDEBUGCODE(this->validate();)
1640 if (dst == NULL) {
1641 dst = (SkPath*)this;
1642 }
1643
tomhudson@google.com8d430182011-06-06 19:11:19 +00001644 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 SkPath tmp;
1646 tmp.fFillType = fFillType;
1647
1648 SkPath::Iter iter(*this, false);
1649 SkPoint pts[4];
1650 SkPath::Verb verb;
1651
reed@google.com4a3b7142012-05-16 17:16:46 +00001652 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 switch (verb) {
1654 case kMove_Verb:
1655 tmp.moveTo(pts[0]);
1656 break;
1657 case kLine_Verb:
1658 tmp.lineTo(pts[1]);
1659 break;
1660 case kQuad_Verb:
1661 subdivide_quad_to(&tmp, pts);
1662 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001663 case kConic_Verb:
1664 SkASSERT(!"TODO: compute new weight");
1665 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1666 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 case kCubic_Verb:
1668 subdivide_cubic_to(&tmp, pts);
1669 break;
1670 case kClose_Verb:
1671 tmp.close();
1672 break;
1673 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001674 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 break;
1676 }
1677 }
1678
1679 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001680 SkPathRef::Editor ed(&dst->fPathRef);
1681 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001682 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001684 /*
1685 * If we're not in perspective, we can transform all of the points at
1686 * once.
1687 *
1688 * Here we also want to optimize bounds, by noting if the bounds are
1689 * already known, and if so, we just transform those as well and mark
1690 * them as "known", rather than force the transformed path to have to
1691 * recompute them.
1692 *
1693 * Special gotchas if the path is effectively empty (<= 1 point) or
1694 * if it is non-finite. In those cases bounds need to stay empty,
1695 * regardless of the matrix.
1696 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001697 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001698 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001699 if (fIsFinite) {
1700 matrix.mapRect(&dst->fBounds, fBounds);
1701 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1702 dst->fBounds.setEmpty();
1703 }
1704 } else {
1705 dst->fIsFinite = false;
1706 dst->fBounds.setEmpty();
1707 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001709 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001710 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 }
1712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001713 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1714
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001717 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001718 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001720
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001721#ifdef SK_BUILD_FOR_ANDROID
1722 if (!matrix.isIdentity()) {
1723 GEN_ID_PTR_INC(dst);
1724 }
1725#endif
1726
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001727 if (kUnknown_Direction == fDirection) {
1728 dst->fDirection = kUnknown_Direction;
1729 } else {
1730 SkScalar det2x2 =
1731 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1732 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1733 if (det2x2 < 0) {
1734 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1735 } else if (det2x2 > 0) {
1736 dst->fDirection = fDirection;
1737 } else {
1738 dst->fDirection = kUnknown_Direction;
1739 }
1740 }
1741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 // It's an oval only if it stays a rect.
1743 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001744
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 SkDEBUGCODE(dst->validate();)
1746 }
1747}
1748
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749///////////////////////////////////////////////////////////////////////////////
1750///////////////////////////////////////////////////////////////////////////////
1751
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001752enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001753 kEmptyContour_SegmentState, // The current contour is empty. We may be
1754 // starting processing or we may have just
1755 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001756 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1757 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1758 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759};
1760
1761SkPath::Iter::Iter() {
1762#ifdef SK_DEBUG
1763 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001764 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001767 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768#endif
1769 // need to init enough to make next() harmlessly return kDone_Verb
1770 fVerbs = NULL;
1771 fVerbStop = NULL;
1772 fNeedClose = false;
1773}
1774
1775SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1776 this->setPath(path, forceClose);
1777}
1778
1779void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001780 fPts = path.fPathRef->points();
1781 fVerbs = path.fPathRef->verbs();
1782 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001783 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001784 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001785 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 fForceClose = SkToU8(forceClose);
1787 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001788 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789}
1790
1791bool SkPath::Iter::isClosedContour() const {
1792 if (fVerbs == NULL || fVerbs == fVerbStop) {
1793 return false;
1794 }
1795 if (fForceClose) {
1796 return true;
1797 }
1798
1799 const uint8_t* verbs = fVerbs;
1800 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001801
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001802 if (kMove_Verb == *(verbs - 1)) {
1803 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 }
1805
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001806 while (verbs > stop) {
1807 // verbs points one beyond the current verb, decrement first.
1808 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 if (kMove_Verb == v) {
1810 break;
1811 }
1812 if (kClose_Verb == v) {
1813 return true;
1814 }
1815 }
1816 return false;
1817}
1818
1819SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001820 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001822 // A special case: if both points are NaN, SkPoint::operation== returns
1823 // false, but the iterator expects that they are treated as the same.
1824 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001825 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1826 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001827 return kClose_Verb;
1828 }
1829
reed@google.com9e25dbf2012-05-15 17:05:38 +00001830 pts[0] = fLastPt;
1831 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 fLastPt = fMoveTo;
1833 fCloseLine = true;
1834 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001835 } else {
1836 pts[0] = fMoveTo;
1837 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839}
1840
reed@google.com9e25dbf2012-05-15 17:05:38 +00001841const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 if (fSegmentState == kAfterMove_SegmentState) {
1843 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001845 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1848 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001849 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851}
1852
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853void SkPath::Iter::consumeDegenerateSegments() {
1854 // We need to step over anything that will not move the current draw point
1855 // forward before the next move is seen
1856 const uint8_t* lastMoveVerb = 0;
1857 const SkPoint* lastMovePt = 0;
1858 SkPoint lastPt = fLastPt;
1859 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 switch (verb) {
1862 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 // Keep a record of this most recent move
1864 lastMoveVerb = fVerbs;
1865 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001866 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fPts++;
1869 break;
1870
1871 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001872 // A close when we are in a segment is always valid except when it
1873 // follows a move which follows a segment.
1874 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 return;
1876 }
1877 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001878 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 break;
1880
1881 case kLine_Verb:
1882 if (!IsLineDegenerate(lastPt, fPts[0])) {
1883 if (lastMoveVerb) {
1884 fVerbs = lastMoveVerb;
1885 fPts = lastMovePt;
1886 return;
1887 }
1888 return;
1889 }
1890 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 fPts++;
1893 break;
1894
reed@google.com277c3f82013-05-31 15:17:50 +00001895 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001896 case kQuad_Verb:
1897 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1898 if (lastMoveVerb) {
1899 fVerbs = lastMoveVerb;
1900 fPts = lastMovePt;
1901 return;
1902 }
1903 return;
1904 }
1905 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001906 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001908 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 break;
1910
1911 case kCubic_Verb:
1912 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1913 if (lastMoveVerb) {
1914 fVerbs = lastMoveVerb;
1915 fPts = lastMovePt;
1916 return;
1917 }
1918 return;
1919 }
1920 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001921 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 fPts += 3;
1923 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001924
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001926 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 }
1928 }
1929}
1930
reed@google.com4a3b7142012-05-16 17:16:46 +00001931SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001932 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 // Close the curve if requested and if there is some curve to close
1936 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001937 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 return kLine_Verb;
1939 }
1940 fNeedClose = false;
1941 return kClose_Verb;
1942 }
1943 return kDone_Verb;
1944 }
1945
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001946 // fVerbs is one beyond the current verb, decrement first
1947 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 const SkPoint* SK_RESTRICT srcPts = fPts;
1949 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950
1951 switch (verb) {
1952 case kMove_Verb:
1953 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001954 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955 verb = this->autoClose(pts);
1956 if (verb == kClose_Verb) {
1957 fNeedClose = false;
1958 }
1959 return (Verb)verb;
1960 }
1961 if (fVerbs == fVerbStop) { // might be a trailing moveto
1962 return kDone_Verb;
1963 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001964 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001965 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001967 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969 fNeedClose = fForceClose;
1970 break;
1971 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001972 pts[0] = this->cons_moveTo();
1973 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 fLastPt = srcPts[0];
1975 fCloseLine = false;
1976 srcPts += 1;
1977 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001978 case kConic_Verb:
1979 fConicWeights += 1;
1980 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001982 pts[0] = this->cons_moveTo();
1983 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 fLastPt = srcPts[1];
1985 srcPts += 2;
1986 break;
1987 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001988 pts[0] = this->cons_moveTo();
1989 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990 fLastPt = srcPts[2];
1991 srcPts += 3;
1992 break;
1993 case kClose_Verb:
1994 verb = this->autoClose(pts);
1995 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001996 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997 } else {
1998 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001999 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002001 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 break;
2003 }
2004 fPts = srcPts;
2005 return (Verb)verb;
2006}
2007
2008///////////////////////////////////////////////////////////////////////////////
2009
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002010SkPath::RawIter::RawIter() {
2011#ifdef SK_DEBUG
2012 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002013 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2015#endif
2016 // need to init enough to make next() harmlessly return kDone_Verb
2017 fVerbs = NULL;
2018 fVerbStop = NULL;
2019}
2020
2021SkPath::RawIter::RawIter(const SkPath& path) {
2022 this->setPath(path);
2023}
2024
2025void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002026 fPts = path.fPathRef->points();
2027 fVerbs = path.fPathRef->verbs();
2028 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002029 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 fMoveTo.fX = fMoveTo.fY = 0;
2031 fLastPt.fX = fLastPt.fY = 0;
2032}
2033
2034SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002035 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002036 if (fVerbs == fVerbStop) {
2037 return kDone_Verb;
2038 }
2039
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002040 // fVerbs points one beyond next verb so decrement first.
2041 unsigned verb = *(--fVerbs);
2042 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002043
2044 switch (verb) {
2045 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002046 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002047 fMoveTo = srcPts[0];
2048 fLastPt = fMoveTo;
2049 srcPts += 1;
2050 break;
2051 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002052 pts[0] = fLastPt;
2053 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002054 fLastPt = srcPts[0];
2055 srcPts += 1;
2056 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002057 case kConic_Verb:
2058 fConicWeights += 1;
2059 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002060 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002061 pts[0] = fLastPt;
2062 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002063 fLastPt = srcPts[1];
2064 srcPts += 2;
2065 break;
2066 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002067 pts[0] = fLastPt;
2068 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002069 fLastPt = srcPts[2];
2070 srcPts += 3;
2071 break;
2072 case kClose_Verb:
2073 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002074 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002075 break;
2076 }
2077 fPts = srcPts;
2078 return (Verb)verb;
2079}
2080
2081///////////////////////////////////////////////////////////////////////////////
2082
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002084 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085*/
2086
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002087uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 SkDEBUGCODE(this->validate();)
2089
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002090 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002091 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002092 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002093 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002094 return SkAlign4(byteCount);
2095 }
2096
2097 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002098
2099 // Call getBounds() to ensure (as a side-effect) that fBounds
2100 // and fIsFinite are computed.
2101 const SkRect& bounds = this->getBounds();
2102 SkASSERT(!fBoundsIsDirty);
2103
2104 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2105 ((fIsOval & 1) << kIsOval_SerializationShift) |
2106 (fConvexity << kConvexity_SerializationShift) |
2107 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002108 (fSegmentMask << kSegmentMask_SerializationShift) |
2109 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002110
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002111 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002112
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002113 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114
2115 buffer.write(&bounds, sizeof(bounds));
2116
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002117 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002118 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119}
2120
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002121uint32_t SkPath::readFromMemory(const void* storage) {
2122 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002123
reed@google.com98b11f12011-09-21 18:40:27 +00002124 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002125 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2126 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2127 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2128 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002129 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002130 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002131
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002132 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002133
2134 buffer.read(&fBounds, sizeof(fBounds));
2135 fBoundsIsDirty = false;
2136
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002137 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002138
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002139 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140
2141 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002142 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143}
2144
2145///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146
reed@google.com51bbe752013-01-17 22:07:50 +00002147#include "SkString.h"
2148
2149static void append_scalar(SkString* str, SkScalar value) {
2150 SkString tmp;
2151 tmp.printf("%g", value);
2152 if (tmp.contains('.')) {
2153 tmp.appendUnichar('f');
2154 }
2155 str->append(tmp);
2156}
2157
2158static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002159 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002160 str->append(label);
2161 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002162
reed@google.com51bbe752013-01-17 22:07:50 +00002163 const SkScalar* values = &pts[0].fX;
2164 count *= 2;
2165
2166 for (int i = 0; i < count; ++i) {
2167 append_scalar(str, values[i]);
2168 if (i < count - 1) {
2169 str->append(", ");
2170 }
2171 }
reed@google.com277c3f82013-05-31 15:17:50 +00002172 if (conicWeight >= 0) {
2173 str->append(", ");
2174 append_scalar(str, conicWeight);
2175 }
reed@google.com51bbe752013-01-17 22:07:50 +00002176 str->append(");\n");
2177}
2178
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179void SkPath::dump(bool forceClose, const char title[]) const {
2180 Iter iter(*this, forceClose);
2181 SkPoint pts[4];
2182 Verb verb;
2183
2184 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2185 title ? title : "");
2186
reed@google.com51bbe752013-01-17 22:07:50 +00002187 SkString builder;
2188
reed@google.com4a3b7142012-05-16 17:16:46 +00002189 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 switch (verb) {
2191 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002192 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 break;
2194 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002195 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196 break;
2197 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002198 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002200 case kConic_Verb:
2201 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2202 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002203 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002204 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002205 break;
2206 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002207 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 break;
2209 default:
2210 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2211 verb = kDone_Verb; // stop the loop
2212 break;
2213 }
2214 }
reed@google.com51bbe752013-01-17 22:07:50 +00002215 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002216}
2217
reed@android.come522ca52009-11-23 20:10:41 +00002218void SkPath::dump() const {
2219 this->dump(false);
2220}
2221
2222#ifdef SK_DEBUG
2223void SkPath::validate() const {
2224 SkASSERT(this != NULL);
2225 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002226
djsollen@google.com077348c2012-10-22 20:23:32 +00002227#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002228 if (!fBoundsIsDirty) {
2229 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002230
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002231 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002232 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002233
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002234 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002235 // if we're empty, fBounds may be empty but translated, so we can't
2236 // necessarily compare to bounds directly
2237 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2238 // be [2, 2, 2, 2]
2239 SkASSERT(bounds.isEmpty());
2240 SkASSERT(fBounds.isEmpty());
2241 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002242 if (bounds.isEmpty()) {
2243 SkASSERT(fBounds.isEmpty());
2244 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002245 if (!fBounds.isEmpty()) {
2246 SkASSERT(fBounds.contains(bounds));
2247 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002248 }
reed@android.come522ca52009-11-23 20:10:41 +00002249 }
2250 }
reed@google.com10296cc2011-09-21 12:29:05 +00002251
2252 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002253 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2254 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2255 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002256 case kLine_Verb:
2257 mask |= kLine_SegmentMask;
2258 break;
2259 case kQuad_Verb:
2260 mask |= kQuad_SegmentMask;
2261 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002262 case kConic_Verb:
2263 mask |= kConic_SegmentMask;
2264 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002265 case kCubic_Verb:
2266 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002267 case kMove_Verb: // these verbs aren't included in the segment mask.
2268 case kClose_Verb:
2269 break;
2270 case kDone_Verb:
2271 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2272 break;
2273 default:
2274 SkDEBUGFAIL("Unknown Verb");
2275 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002276 }
2277 }
2278 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002279#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002280}
djsollen@google.com077348c2012-10-22 20:23:32 +00002281#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002282
reed@google.com04863fa2011-05-15 04:08:24 +00002283///////////////////////////////////////////////////////////////////////////////
2284
reed@google.com0b7b9822011-05-16 12:29:27 +00002285static int sign(SkScalar x) { return x < 0; }
2286#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002287
reed@google.com04863fa2011-05-15 04:08:24 +00002288static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002289 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002290}
2291
2292// only valid for a single contour
2293struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002294 Convexicator()
2295 : fPtCount(0)
2296 , fConvexity(SkPath::kConvex_Convexity)
2297 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002298 fSign = 0;
2299 // warnings
2300 fCurrPt.set(0, 0);
2301 fVec0.set(0, 0);
2302 fVec1.set(0, 0);
2303 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002304
2305 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002306 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 }
2308
2309 SkPath::Convexity getConvexity() const { return fConvexity; }
2310
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002311 /** The direction returned is only valid if the path is determined convex */
2312 SkPath::Direction getDirection() const { return fDirection; }
2313
reed@google.com04863fa2011-05-15 04:08:24 +00002314 void addPt(const SkPoint& pt) {
2315 if (SkPath::kConcave_Convexity == fConvexity) {
2316 return;
2317 }
2318
2319 if (0 == fPtCount) {
2320 fCurrPt = pt;
2321 ++fPtCount;
2322 } else {
2323 SkVector vec = pt - fCurrPt;
2324 if (vec.fX || vec.fY) {
2325 fCurrPt = pt;
2326 if (++fPtCount == 2) {
2327 fFirstVec = fVec1 = vec;
2328 } else {
2329 SkASSERT(fPtCount > 2);
2330 this->addVec(vec);
2331 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002332
reed@google.com85b6e392011-05-15 20:25:17 +00002333 int sx = sign(vec.fX);
2334 int sy = sign(vec.fY);
2335 fDx += (sx != fSx);
2336 fDy += (sy != fSy);
2337 fSx = sx;
2338 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002339
reed@google.com85b6e392011-05-15 20:25:17 +00002340 if (fDx > 3 || fDy > 3) {
2341 fConvexity = SkPath::kConcave_Convexity;
2342 }
reed@google.com04863fa2011-05-15 04:08:24 +00002343 }
2344 }
2345 }
2346
2347 void close() {
2348 if (fPtCount > 2) {
2349 this->addVec(fFirstVec);
2350 }
2351 }
2352
2353private:
2354 void addVec(const SkVector& vec) {
2355 SkASSERT(vec.fX || vec.fY);
2356 fVec0 = fVec1;
2357 fVec1 = vec;
2358 int sign = CrossProductSign(fVec0, fVec1);
2359 if (0 == fSign) {
2360 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002361 if (1 == sign) {
2362 fDirection = SkPath::kCW_Direction;
2363 } else if (-1 == sign) {
2364 fDirection = SkPath::kCCW_Direction;
2365 }
reed@google.com04863fa2011-05-15 04:08:24 +00002366 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002367 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002368 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002369 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 }
2371 }
2372 }
2373
2374 SkPoint fCurrPt;
2375 SkVector fVec0, fVec1, fFirstVec;
2376 int fPtCount; // non-degenerate points
2377 int fSign;
2378 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002379 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002380 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002381};
2382
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002383SkPath::Convexity SkPath::internalGetConvexity() const {
2384 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002385 SkPoint pts[4];
2386 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002387 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002388
2389 int contourCount = 0;
2390 int count;
2391 Convexicator state;
2392
2393 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2394 switch (verb) {
2395 case kMove_Verb:
2396 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002397 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002398 return kConcave_Convexity;
2399 }
2400 pts[1] = pts[0];
2401 count = 1;
2402 break;
2403 case kLine_Verb: count = 1; break;
2404 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002405 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 case kCubic_Verb: count = 3; break;
2407 case kClose_Verb:
2408 state.close();
2409 count = 0;
2410 break;
2411 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002412 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002413 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 return kConcave_Convexity;
2415 }
2416
2417 for (int i = 1; i <= count; i++) {
2418 state.addPt(pts[i]);
2419 }
2420 // early exit
2421 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 return kConcave_Convexity;
2424 }
2425 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002426 fConvexity = state.getConvexity();
2427 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2428 fDirection = state.getDirection();
2429 }
2430 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002431}
reed@google.com69a99432012-01-10 18:00:10 +00002432
2433///////////////////////////////////////////////////////////////////////////////
2434
2435class ContourIter {
2436public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002437 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002438
2439 bool done() const { return fDone; }
2440 // if !done() then these may be called
2441 int count() const { return fCurrPtCount; }
2442 const SkPoint* pts() const { return fCurrPt; }
2443 void next();
2444
2445private:
2446 int fCurrPtCount;
2447 const SkPoint* fCurrPt;
2448 const uint8_t* fCurrVerb;
2449 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002450 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002451 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002452 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002453};
2454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002455ContourIter::ContourIter(const SkPathRef& pathRef) {
2456 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002457 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002458 fCurrPt = pathRef.points();
2459 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002460 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002461 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002462 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002463 this->next();
2464}
2465
2466void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002467 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002468 fDone = true;
2469 }
2470 if (fDone) {
2471 return;
2472 }
2473
2474 // skip pts of prev contour
2475 fCurrPt += fCurrPtCount;
2476
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002477 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002478 int ptCount = 1; // moveTo
2479 const uint8_t* verbs = fCurrVerb;
2480
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002481 for (--verbs; verbs > fStopVerbs; --verbs) {
2482 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002483 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002484 goto CONTOUR_END;
2485 case SkPath::kLine_Verb:
2486 ptCount += 1;
2487 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002488 case SkPath::kConic_Verb:
2489 fCurrConicWeight += 1;
2490 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002491 case SkPath::kQuad_Verb:
2492 ptCount += 2;
2493 break;
2494 case SkPath::kCubic_Verb:
2495 ptCount += 3;
2496 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002497 case SkPath::kClose_Verb:
2498 break;
2499 default:
2500 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002501 break;
2502 }
2503 }
2504CONTOUR_END:
2505 fCurrPtCount = ptCount;
2506 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002507 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002508}
2509
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002510// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002511static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002512 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2513 // We may get 0 when the above subtracts underflow. We expect this to be
2514 // very rare and lazily promote to double.
2515 if (0 == cross) {
2516 double p0x = SkScalarToDouble(p0.fX);
2517 double p0y = SkScalarToDouble(p0.fY);
2518
2519 double p1x = SkScalarToDouble(p1.fX);
2520 double p1y = SkScalarToDouble(p1.fY);
2521
2522 double p2x = SkScalarToDouble(p2.fX);
2523 double p2y = SkScalarToDouble(p2.fY);
2524
2525 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2526 (p1y - p0y) * (p2x - p0x));
2527
2528 }
2529 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002530}
2531
reed@google.comc1ea60a2012-01-31 15:15:36 +00002532// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002533static int find_max_y(const SkPoint pts[], int count) {
2534 SkASSERT(count > 0);
2535 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002536 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002537 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002538 SkScalar y = pts[i].fY;
2539 if (y > max) {
2540 max = y;
2541 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002542 }
2543 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002544 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002545}
2546
reed@google.comcabaf1d2012-01-11 21:03:05 +00002547static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2548 int i = index;
2549 for (;;) {
2550 i = (i + inc) % n;
2551 if (i == index) { // we wrapped around, so abort
2552 break;
2553 }
2554 if (pts[index] != pts[i]) { // found a different point, success!
2555 break;
2556 }
2557 }
2558 return i;
2559}
2560
reed@google.comc1ea60a2012-01-31 15:15:36 +00002561/**
2562 * Starting at index, and moving forward (incrementing), find the xmin and
2563 * xmax of the contiguous points that have the same Y.
2564 */
2565static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2566 int* maxIndexPtr) {
2567 const SkScalar y = pts[index].fY;
2568 SkScalar min = pts[index].fX;
2569 SkScalar max = min;
2570 int minIndex = index;
2571 int maxIndex = index;
2572 for (int i = index + 1; i < n; ++i) {
2573 if (pts[i].fY != y) {
2574 break;
2575 }
2576 SkScalar x = pts[i].fX;
2577 if (x < min) {
2578 min = x;
2579 minIndex = i;
2580 } else if (x > max) {
2581 max = x;
2582 maxIndex = i;
2583 }
2584 }
2585 *maxIndexPtr = maxIndex;
2586 return minIndex;
2587}
2588
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002589static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002590 if (dir) {
2591 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2592 }
reed@google.comac8543f2012-01-30 20:51:25 +00002593}
2594
reed@google.comc1ea60a2012-01-31 15:15:36 +00002595#if 0
2596#include "SkString.h"
2597#include "../utils/SkParsePath.h"
2598static void dumpPath(const SkPath& path) {
2599 SkString str;
2600 SkParsePath::ToSVGString(path, &str);
2601 SkDebugf("%s\n", str.c_str());
2602}
2603#endif
2604
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002605namespace {
2606// for use with convex_dir_test
2607double mul(double a, double b) { return a * b; }
2608SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2609double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2610SkScalar toScalar(SkScalar a) { return a; }
2611
2612// determines the winding direction of a convex polygon with the precision
2613// of T. CAST_SCALAR casts an SkScalar to T.
2614template <typename T, T (CAST_SCALAR)(SkScalar)>
2615bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2616 // we find the first three points that form a non-degenerate
2617 // triangle. If there are no such points then the path is
2618 // degenerate. The first is always point 0. Now we find the second
2619 // point.
2620 int i = 0;
2621 enum { kX = 0, kY = 1 };
2622 T v0[2];
2623 while (1) {
2624 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2625 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2626 if (v0[kX] || v0[kY]) {
2627 break;
2628 }
2629 if (++i == n - 1) {
2630 return false;
2631 }
2632 }
2633 // now find a third point that is not colinear with the first two
2634 // points and check the orientation of the triangle (which will be
2635 // the same as the orientation of the path).
2636 for (++i; i < n; ++i) {
2637 T v1[2];
2638 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2639 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2640 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2641 if (0 != cross) {
2642 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2643 return true;
2644 }
2645 }
2646 return false;
2647}
2648}
2649
reed@google.comac8543f2012-01-30 20:51:25 +00002650/*
2651 * We loop through all contours, and keep the computed cross-product of the
2652 * contour that contained the global y-max. If we just look at the first
2653 * contour, we may find one that is wound the opposite way (correctly) since
2654 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2655 * that is outer most (or at least has the global y-max) before we can consider
2656 * its cross product.
2657 */
reed@google.com69a99432012-01-10 18:00:10 +00002658bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002659// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002660 // don't want to pay the cost for computing this if it
2661 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002662
2663 if (kUnknown_Direction != fDirection) {
2664 *dir = static_cast<Direction>(fDirection);
2665 return true;
2666 }
reed@google.com69a99432012-01-10 18:00:10 +00002667 const Convexity conv = this->getConvexityOrUnknown();
2668
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002669 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002670
reed@google.comac8543f2012-01-30 20:51:25 +00002671 // initialize with our logical y-min
2672 SkScalar ymax = this->getBounds().fTop;
2673 SkScalar ymaxCross = 0;
2674
reed@google.com69a99432012-01-10 18:00:10 +00002675 for (; !iter.done(); iter.next()) {
2676 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002677 if (n < 3) {
2678 continue;
2679 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002680
reed@google.comcabaf1d2012-01-11 21:03:05 +00002681 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002682 SkScalar cross = 0;
2683 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684 // We try first at scalar precision, and then again at double
2685 // precision. This is because the vectors computed between distant
2686 // points may lose too much precision.
2687 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002688 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002689 return true;
2690 }
2691 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002692 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002693 return true;
2694 } else {
2695 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002696 }
2697 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002698 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002699 if (pts[index].fY < ymax) {
2700 continue;
2701 }
2702
reed@google.comc1ea60a2012-01-31 15:15:36 +00002703 // If there is more than 1 distinct point at the y-max, we take the
2704 // x-min and x-max of them and just subtract to compute the dir.
2705 if (pts[(index + 1) % n].fY == pts[index].fY) {
2706 int maxIndex;
2707 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002708 if (minIndex == maxIndex) {
2709 goto TRY_CROSSPROD;
2710 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002711 SkASSERT(pts[minIndex].fY == pts[index].fY);
2712 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2713 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2714 // we just subtract the indices, and let that auto-convert to
2715 // SkScalar, since we just want - or + to signal the direction.
2716 cross = minIndex - maxIndex;
2717 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002718 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002719 // Find a next and prev index to use for the cross-product test,
2720 // but we try to find pts that form non-zero vectors from pts[index]
2721 //
2722 // Its possible that we can't find two non-degenerate vectors, so
2723 // we have to guard our search (e.g. all the pts could be in the
2724 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
reed@google.comc1ea60a2012-01-31 15:15:36 +00002726 // we pass n - 1 instead of -1 so we don't foul up % operator by
2727 // passing it a negative LH argument.
2728 int prev = find_diff_pt(pts, index, n, n - 1);
2729 if (prev == index) {
2730 // completely degenerate, skip to next contour
2731 continue;
2732 }
2733 int next = find_diff_pt(pts, index, n, 1);
2734 SkASSERT(next != index);
2735 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002736 // 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 +00002737 // x-direction. We really should continue to walk away from the degeneracy until
2738 // there is a divergence.
2739 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002740 // construct the subtract so we get the correct Direction below
2741 cross = pts[index].fX - pts[next].fX;
2742 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002743 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002744
reed@google.comac8543f2012-01-30 20:51:25 +00002745 if (cross) {
2746 // record our best guess so far
2747 ymax = pts[index].fY;
2748 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002749 }
reed@google.com69a99432012-01-10 18:00:10 +00002750 }
2751 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002752 if (ymaxCross) {
2753 crossToDir(ymaxCross, dir);
2754 fDirection = *dir;
2755 return true;
2756 } else {
2757 return false;
2758 }
reed@google.comac8543f2012-01-30 20:51:25 +00002759}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760
2761///////////////////////////////////////////////////////////////////////////////
2762
2763static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2764 SkScalar D, SkScalar t) {
2765 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2766}
2767
2768static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2769 SkScalar t) {
2770 SkScalar A = c3 + 3*(c1 - c2) - c0;
2771 SkScalar B = 3*(c2 - c1 - c1 + c0);
2772 SkScalar C = 3*(c1 - c0);
2773 SkScalar D = c0;
2774 return eval_cubic_coeff(A, B, C, D, t);
2775}
2776
2777/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2778 t value such that cubic(t) = target
2779 */
2780static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2781 SkScalar target, SkScalar* t) {
2782 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2783 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002784
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 SkScalar D = c0 - target;
2786 SkScalar A = c3 + 3*(c1 - c2) - c0;
2787 SkScalar B = 3*(c2 - c1 - c1 + c0);
2788 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002789
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002790 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2791 SkScalar minT = 0;
2792 SkScalar maxT = SK_Scalar1;
2793 SkScalar mid;
2794 int i;
2795 for (i = 0; i < 16; i++) {
2796 mid = SkScalarAve(minT, maxT);
2797 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2798 if (delta < 0) {
2799 minT = mid;
2800 delta = -delta;
2801 } else {
2802 maxT = mid;
2803 }
2804 if (delta < TOLERANCE) {
2805 break;
2806 }
2807 }
2808 *t = mid;
2809 return true;
2810}
2811
2812template <size_t N> static void find_minmax(const SkPoint pts[],
2813 SkScalar* minPtr, SkScalar* maxPtr) {
2814 SkScalar min, max;
2815 min = max = pts[0].fX;
2816 for (size_t i = 1; i < N; ++i) {
2817 min = SkMinScalar(min, pts[i].fX);
2818 max = SkMaxScalar(max, pts[i].fX);
2819 }
2820 *minPtr = min;
2821 *maxPtr = max;
2822}
2823
2824static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2825 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 int dir = 1;
2828 if (pts[0].fY > pts[3].fY) {
2829 storage[0] = pts[3];
2830 storage[1] = pts[2];
2831 storage[2] = pts[1];
2832 storage[3] = pts[0];
2833 pts = storage;
2834 dir = -1;
2835 }
2836 if (y < pts[0].fY || y >= pts[3].fY) {
2837 return 0;
2838 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002839
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 // quickreject or quickaccept
2841 SkScalar min, max;
2842 find_minmax<4>(pts, &min, &max);
2843 if (x < min) {
2844 return 0;
2845 }
2846 if (x > max) {
2847 return dir;
2848 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002849
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002850 // compute the actual x(t) value
2851 SkScalar t, xt;
2852 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2853 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2854 } else {
2855 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2856 xt = y < mid ? pts[0].fX : pts[3].fX;
2857 }
2858 return xt < x ? dir : 0;
2859}
2860
2861static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2862 SkPoint dst[10];
2863 int n = SkChopCubicAtYExtrema(pts, dst);
2864 int w = 0;
2865 for (int i = 0; i <= n; ++i) {
2866 w += winding_mono_cubic(&dst[i * 3], x, y);
2867 }
2868 return w;
2869}
2870
2871static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2872 SkScalar y0 = pts[0].fY;
2873 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 int dir = 1;
2876 if (y0 > y2) {
2877 SkTSwap(y0, y2);
2878 dir = -1;
2879 }
2880 if (y < y0 || y >= y2) {
2881 return 0;
2882 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002883
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 // bounds check on X (not required. is it faster?)
2885#if 0
2886 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2887 return 0;
2888 }
2889#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 SkScalar roots[2];
2892 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2893 2 * (pts[1].fY - pts[0].fY),
2894 pts[0].fY - y,
2895 roots);
2896 SkASSERT(n <= 1);
2897 SkScalar xt;
2898 if (0 == n) {
2899 SkScalar mid = SkScalarAve(y0, y2);
2900 // Need [0] and [2] if dir == 1
2901 // and [2] and [0] if dir == -1
2902 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2903 } else {
2904 SkScalar t = roots[0];
2905 SkScalar C = pts[0].fX;
2906 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2907 SkScalar B = 2 * (pts[1].fX - C);
2908 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2909 }
2910 return xt < x ? dir : 0;
2911}
2912
2913static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2914 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2915 if (y0 == y1) {
2916 return true;
2917 }
2918 if (y0 < y1) {
2919 return y1 <= y2;
2920 } else {
2921 return y1 >= y2;
2922 }
2923}
2924
2925static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2926 SkPoint dst[5];
2927 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002928
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2930 n = SkChopQuadAtYExtrema(pts, dst);
2931 pts = dst;
2932 }
2933 int w = winding_mono_quad(pts, x, y);
2934 if (n > 0) {
2935 w += winding_mono_quad(&pts[2], x, y);
2936 }
2937 return w;
2938}
2939
2940static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2941 SkScalar x0 = pts[0].fX;
2942 SkScalar y0 = pts[0].fY;
2943 SkScalar x1 = pts[1].fX;
2944 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002945
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002946 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002947
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 int dir = 1;
2949 if (y0 > y1) {
2950 SkTSwap(y0, y1);
2951 dir = -1;
2952 }
2953 if (y < y0 || y >= y1) {
2954 return 0;
2955 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002956
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2958 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002959
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 if (SkScalarSignAsInt(cross) == dir) {
2961 dir = 0;
2962 }
2963 return dir;
2964}
2965
2966bool SkPath::contains(SkScalar x, SkScalar y) const {
2967 bool isInverse = this->isInverseFillType();
2968 if (this->isEmpty()) {
2969 return isInverse;
2970 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002971
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 const SkRect& bounds = this->getBounds();
2973 if (!bounds.contains(x, y)) {
2974 return isInverse;
2975 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 SkPath::Iter iter(*this, true);
2978 bool done = false;
2979 int w = 0;
2980 do {
2981 SkPoint pts[4];
2982 switch (iter.next(pts, false)) {
2983 case SkPath::kMove_Verb:
2984 case SkPath::kClose_Verb:
2985 break;
2986 case SkPath::kLine_Verb:
2987 w += winding_line(pts, x, y);
2988 break;
2989 case SkPath::kQuad_Verb:
2990 w += winding_quad(pts, x, y);
2991 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002992 case SkPath::kConic_Verb:
2993 SkASSERT(0);
2994 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 case SkPath::kCubic_Verb:
2996 w += winding_cubic(pts, x, y);
2997 break;
2998 case SkPath::kDone_Verb:
2999 done = true;
3000 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003001 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003003
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 switch (this->getFillType()) {
3005 case SkPath::kEvenOdd_FillType:
3006 case SkPath::kInverseEvenOdd_FillType:
3007 w &= 1;
3008 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003009 default:
3010 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 }
3012 return SkToBool(w);
3013}