blob: 5f53ce89153d1f4297b08ec9fd24c769bc546b24 [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
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 cached bounds), and then 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
robertphillips@google.comca0c8382013-09-26 12:18:23 +000085 convex (which is always true since we only allow the addition of rects).
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() {
reed@google.com44699382013-10-31 17:28:30 +0000100 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
101 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 if (fEmpty || fHasValidBounds) {
103 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105 }
reed@google.comabf15c12011-01-18 20:35:51 +0000106
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000108 SkPath* fPath;
109 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000111 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000112 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000113
reed@android.com6b82d1a2009-06-03 02:35:01 +0000114 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000115 // Cannot use fRect for our bounds unless we know it is sorted
116 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000118 // Mark the path's bounds as dirty if (1) they are, or (2) the path
119 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000120 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000122 if (fHasValidBounds && !fEmpty) {
123 joinNoEmptyChecks(&fRect, fPath->getBounds());
124 }
125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 }
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129////////////////////////////////////////////////////////////////////////////
130
131/*
132 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000133 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
135
136 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000137 1. If we encounter degenerate segments, remove them
138 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
139 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
140 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141*/
142
143////////////////////////////////////////////////////////////////////////////
144
reed@google.comd335d1d2012-01-12 18:17:11 +0000145// flag to require a moveTo if we begin with something else, like lineTo etc.
146#define INITIAL_LASTMOVETOINDEX_VALUE ~0
147
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000148SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000149 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000151 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000152#endif
153{
154 this->resetFields();
155}
156
157void SkPath::resetFields() {
158 //fPathRef is assumed to have been emptied by the caller.
159 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
160 fFillType = kWinding_FillType;
161 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000162 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000163 fDirection = kUnknown_Direction;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000164 fIsOval = false;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000165
166 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
167 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000168}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000171 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000173#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000174 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000175#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177}
178
179SkPath::~SkPath() {
180 SkDEBUGCODE(this->validate();)
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183SkPath& SkPath::operator=(const SkPath& that) {
184 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 if (this != &that) {
187 fPathRef.reset(SkRef(that.fPathRef.get()));
188 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000189#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000190 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000191#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 }
193 SkDEBUGCODE(this->validate();)
194 return *this;
195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::copyFields(const SkPath& that) {
198 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 fLastMoveToIndex = that.fLastMoveToIndex;
200 fFillType = that.fFillType;
201 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 fConvexity = that.fConvexity;
203 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205}
206
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000207bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000208 // note: don't need to look at isConvex or bounds, since just comparing the
209 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000210
211 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
212 // since it is only a cache of info in the fVerbs, but its a fast way to
213 // notice a difference
214
reed@android.com3abec1d2009-03-02 05:36:20 +0000215 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000216 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000217 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000218}
219
bungeman@google.coma5809a32013-06-21 15:13:34 +0000220void SkPath::swap(SkPath& that) {
221 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222
bungeman@google.coma5809a32013-06-21 15:13:34 +0000223 if (this != &that) {
224 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000225 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
226 SkTSwap<uint8_t>(fFillType, that.fFillType);
227 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
229 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000230 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000231#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000232 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
233#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 }
235}
236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237static inline bool check_edge_against_rect(const SkPoint& p0,
238 const SkPoint& p1,
239 const SkRect& rect,
240 SkPath::Direction dir) {
241 const SkPoint* edgeBegin;
242 SkVector v;
243 if (SkPath::kCW_Direction == dir) {
244 v = p1 - p0;
245 edgeBegin = &p0;
246 } else {
247 v = p0 - p1;
248 edgeBegin = &p1;
249 }
250 if (v.fX || v.fY) {
251 // check the cross product of v with the vec from edgeBegin to each rect corner
252 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
253 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
254 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
255 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
256 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
257 return false;
258 }
259 }
260 return true;
261}
262
263bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
264 // This only handles non-degenerate convex paths currently.
265 if (kConvex_Convexity != this->getConvexity()) {
266 return false;
267 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000268
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000269 Direction direction;
270 if (!this->cheapComputeDirection(&direction)) {
271 return false;
272 }
273
274 SkPoint firstPt;
275 SkPoint prevPt;
276 RawIter iter(*this);
277 SkPath::Verb verb;
278 SkPoint pts[4];
279 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(int segmentCount = 0;)
281 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282
283 while ((verb = iter.next(pts)) != kDone_Verb) {
284 int nextPt = -1;
285 switch (verb) {
286 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 SkDEBUGCODE(++moveCnt);
289 firstPt = prevPt = pts[0];
290 break;
291 case kLine_Verb:
292 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(moveCnt && !closeCount);
294 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 break;
296 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000297 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkASSERT(moveCnt && !closeCount);
299 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 nextPt = 2;
301 break;
302 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(moveCnt && !closeCount);
304 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 nextPt = 3;
306 break;
307 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000308 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 break;
310 default:
311 SkDEBUGFAIL("unknown verb");
312 }
313 if (-1 != nextPt) {
314 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
315 return false;
316 }
317 prevPt = pts[nextPt];
318 }
319 }
320
321 return check_edge_against_rect(prevPt, firstPt, rect, direction);
322}
323
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000324uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000325 uint32_t genID = fPathRef->genID();
326#ifdef SK_BUILD_FOR_ANDROID
327 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
328 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
329#endif
330 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000331}
djsollen@google.come63793a2012-03-21 15:39:03 +0000332
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000333#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000334const SkPath* SkPath::getSourcePath() const {
335 return fSourcePath;
336}
337
338void SkPath::setSourcePath(const SkPath* path) {
339 fSourcePath = path;
340}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000341#endif
342
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343void SkPath::reset() {
344 SkDEBUGCODE(this->validate();)
345
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000346 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000347 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348}
349
350void SkPath::rewind() {
351 SkDEBUGCODE(this->validate();)
352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000353 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000354 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
reed@google.com7e6c4d12012-05-10 14:05:43 +0000357bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000358 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000359
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000360 if (2 == verbCount) {
361 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
362 if (kLine_Verb == fPathRef->atVerb(1)) {
363 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000364 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000365 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000366 line[0] = pts[0];
367 line[1] = pts[1];
368 }
369 return true;
370 }
371 }
372 return false;
373}
374
caryclark@google.comf1316942011-07-26 19:54:45 +0000375/*
376 Determines if path is a rect by keeping track of changes in direction
377 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The direction is computed such that:
380 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000381 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
caryclark@google.comf1316942011-07-26 19:54:45 +0000385A rectangle cycles up/right/down/left or up/left/down/right.
386
387The test fails if:
388 The path is closed, and followed by a line.
389 A second move creates a new endpoint.
390 A diagonal line is parsed.
391 There's more than four changes of direction.
392 There's a discontinuity on the line (e.g., a move in the middle)
393 The line reverses direction.
394 The rectangle doesn't complete a cycle.
395 The path contains a quadratic or cubic.
396 The path contains fewer than four points.
397 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000398
caryclark@google.comf1316942011-07-26 19:54:45 +0000399It's OK if the path has:
400 Several colinear line segments composing a rectangle side.
401 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000402
caryclark@google.comf1316942011-07-26 19:54:45 +0000403The direction takes advantage of the corners found since opposite sides
404must travel in opposite directions.
405
406FIXME: Allow colinear quads and cubics to be treated like lines.
407FIXME: If the API passes fill-only, return true if the filled stroke
408 is a rectangle, though the caller failed to close the path.
409 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000410bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
411 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000412 int corners = 0;
413 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000414 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000415 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000416 first.set(0, 0);
417 last.set(0, 0);
418 int firstDirection = 0;
419 int lastDirection = 0;
420 int nextDirection = 0;
421 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000423 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000424 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
425 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000426 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000427 savePts = pts;
428 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 autoClose = true;
430 case kLine_Verb: {
431 SkScalar left = last.fX;
432 SkScalar top = last.fY;
433 SkScalar right = pts->fX;
434 SkScalar bottom = pts->fY;
435 ++pts;
436 if (left != right && top != bottom) {
437 return false; // diagonal
438 }
439 if (left == right && top == bottom) {
440 break; // single point on side OK
441 }
442 nextDirection = (left != right) << 0 |
443 (left < right || top < bottom) << 1;
444 if (0 == corners) {
445 firstDirection = nextDirection;
446 first = last;
447 last = pts[-1];
448 corners = 1;
449 closedOrMoved = false;
450 break;
451 }
452 if (closedOrMoved) {
453 return false; // closed followed by a line
454 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000455 if (autoClose && nextDirection == firstDirection) {
456 break; // colinear with first
457 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 closedOrMoved = autoClose;
459 if (lastDirection != nextDirection) {
460 if (++corners > 4) {
461 return false; // too many direction changes
462 }
463 }
464 last = pts[-1];
465 if (lastDirection == nextDirection) {
466 break; // colinear segment
467 }
468 // Possible values for corners are 2, 3, and 4.
469 // When corners == 3, nextDirection opposes firstDirection.
470 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000471 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
473 if ((directionCycle ^ turn) != nextDirection) {
474 return false; // direction didn't follow cycle
475 }
476 break;
477 }
478 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000479 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 case kCubic_Verb:
481 return false; // quadratic, cubic not allowed
482 case kMove_Verb:
483 last = *pts++;
484 closedOrMoved = true;
485 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000486 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000487 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000488 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000490 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 lastDirection = nextDirection;
492 }
493 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000494 bool result = 4 == corners && (first == last || autoClose);
495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000507bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 SkDEBUGCODE(this->validate();)
509 int currVerb = 0;
510 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000511 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
512 if (result && rect) {
513 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515 return result;
516}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000517
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518bool SkPath::isRect(bool* isClosed, Direction* direction) const {
519 SkDEBUGCODE(this->validate();)
520 int currVerb = 0;
521 const SkPoint* pts = fPathRef->points();
522 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000523}
524
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000525bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 SkDEBUGCODE(this->validate();)
527 int currVerb = 0;
528 const SkPoint* pts = fPathRef->points();
529 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000530 Direction testDirs[2];
531 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 return false;
533 }
534 const SkPoint* last = pts;
535 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000537 testRects[0].set(first, SkToS32(last - first));
538 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 if (testRects[0].contains(testRects[1])) {
540 if (rects) {
541 rects[0] = testRects[0];
542 rects[1] = testRects[1];
543 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (dirs) {
545 dirs[0] = testDirs[0];
546 dirs[1] = testDirs[1];
547 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return true;
549 }
550 if (testRects[1].contains(testRects[0])) {
551 if (rects) {
552 rects[0] = testRects[1];
553 rects[1] = testRects[0];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[1];
557 dirs[1] = testDirs[0];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 }
562 return false;
563}
564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000565int SkPath::countPoints() const {
566 return fPathRef->countPoints();
567}
568
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000569int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 SkDEBUGCODE(this->validate();)
571
572 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000573 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000574 int count = SkMin32(max, fPathRef->countPoints());
575 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
576 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577}
578
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000579SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000580 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
581 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000582 }
583 return SkPoint::Make(0, 0);
584}
585
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000586int SkPath::countVerbs() const {
587 return fPathRef->countVerbs();
588}
589
590static inline void copy_verbs_reverse(uint8_t* inorderDst,
591 const uint8_t* reversedSrc,
592 int count) {
593 for (int i = 0; i < count; ++i) {
594 inorderDst[i] = reversedSrc[~i];
595 }
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getVerbs(uint8_t dst[], int max) const {
599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countVerbs());
604 copy_verbs_reverse(dst, fPathRef->verbs(), count);
605 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606}
607
reed@google.com294dd7b2011-10-11 11:58:32 +0000608bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 SkDEBUGCODE(this->validate();)
610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000612 if (count > 0) {
613 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000616 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 if (lastPt) {
619 lastPt->set(0, 0);
620 }
621 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622}
623
624void SkPath::setLastPt(SkScalar x, SkScalar y) {
625 SkDEBUGCODE(this->validate();)
626
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 if (count == 0) {
629 this->moveTo(x, y);
630 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000631 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 SkPathRef::Editor ed(&fPathRef);
633 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
635}
636
reed@google.com04863fa2011-05-15 04:08:24 +0000637void SkPath::setConvexity(Convexity c) {
638 if (fConvexity != c) {
639 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000640 }
641}
642
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643//////////////////////////////////////////////////////////////////////////////
644// Construction methods
645
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000646#define DIRTY_AFTER_EDIT \
647 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000648 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000649 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000650 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000651 } while (0)
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653void SkPath::incReserve(U16CPU inc) {
654 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 SkDEBUGCODE(this->validate();)
657}
658
659void SkPath::moveTo(SkScalar x, SkScalar y) {
660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663
reed@google.comd335d1d2012-01-12 18:17:11 +0000664 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000665 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000666
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000667 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668}
669
670void SkPath::rMoveTo(SkScalar x, SkScalar y) {
671 SkPoint pt;
672 this->getLastPt(&pt);
673 this->moveTo(pt.fX + x, pt.fY + y);
674}
675
reed@google.comd335d1d2012-01-12 18:17:11 +0000676void SkPath::injectMoveToIfNeeded() {
677 if (fLastMoveToIndex < 0) {
678 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000680 x = y = 0;
681 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000683 x = pt.fX;
684 y = pt.fY;
685 }
686 this->moveTo(x, y);
687 }
688}
689
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690void SkPath::lineTo(SkScalar x, SkScalar y) {
691 SkDEBUGCODE(this->validate();)
692
reed@google.comd335d1d2012-01-12 18:17:11 +0000693 this->injectMoveToIfNeeded();
694
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor ed(&fPathRef);
696 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000697 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698
reed@google.comb54455e2011-05-16 14:16:04 +0000699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000703 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 SkPoint pt;
705 this->getLastPt(&pt);
706 this->lineTo(pt.fX + x, pt.fY + y);
707}
708
709void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
710 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000711
reed@google.comd335d1d2012-01-12 18:17:11 +0000712 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 pts[0].set(x1, y1);
717 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000718 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000719
reed@google.comb54455e2011-05-16 14:16:04 +0000720 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721}
722
723void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000724 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 SkPoint pt;
726 this->getLastPt(&pt);
727 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
728}
729
reed@google.com277c3f82013-05-31 15:17:50 +0000730void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
731 SkScalar w) {
732 // check for <= 0 or NaN with this test
733 if (!(w > 0)) {
734 this->lineTo(x2, y2);
735 } else if (!SkScalarIsFinite(w)) {
736 this->lineTo(x1, y1);
737 this->lineTo(x2, y2);
738 } else if (SK_Scalar1 == w) {
739 this->quadTo(x1, y1, x2, y2);
740 } else {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
reed@google.com277c3f82013-05-31 15:17:50 +0000743 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000744
reed@google.com277c3f82013-05-31 15:17:50 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForConic(w);
747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
749 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
reed@google.com277c3f82013-05-31 15:17:50 +0000751 DIRTY_AFTER_EDIT;
752 }
753}
754
755void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
756 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000757 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
761}
762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
764 SkScalar x3, SkScalar y3) {
765 SkDEBUGCODE(this->validate();)
766
reed@google.comd335d1d2012-01-12 18:17:11 +0000767 this->injectMoveToIfNeeded();
768
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 SkPathRef::Editor ed(&fPathRef);
770 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771 pts[0].set(x1, y1);
772 pts[1].set(x2, y2);
773 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000774 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775
reed@google.comb54455e2011-05-16 14:16:04 +0000776 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777}
778
779void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000781 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 SkPoint pt;
783 this->getLastPt(&pt);
784 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
785 pt.fX + x3, pt.fY + y3);
786}
787
788void SkPath::close() {
789 SkDEBUGCODE(this->validate();)
790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000791 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 case kLine_Verb:
795 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000796 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 case kMove_Verb: {
799 SkPathRef::Editor ed(&fPathRef);
800 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 }
reed@google.com277c3f82013-05-31 15:17:50 +0000803 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000804 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000805 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000806 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000807 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000808 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 }
810 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000811
812 // signal that we need a moveTo to follow us (unless we're done)
813#if 0
814 if (fLastMoveToIndex >= 0) {
815 fLastMoveToIndex = ~fLastMoveToIndex;
816 }
817#else
818 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
819#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820}
821
822///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000823
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000824static void assert_known_direction(int dir) {
825 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
826}
827
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828void SkPath::addRect(const SkRect& rect, Direction dir) {
829 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
830}
831
832void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
833 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000834 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000835 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
836 SkAutoDisableDirectionCheck addc(this);
837
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
839
840 this->incReserve(5);
841
842 this->moveTo(left, top);
843 if (dir == kCCW_Direction) {
844 this->lineTo(left, bottom);
845 this->lineTo(right, bottom);
846 this->lineTo(right, top);
847 } else {
848 this->lineTo(right, top);
849 this->lineTo(right, bottom);
850 this->lineTo(left, bottom);
851 }
852 this->close();
853}
854
reed@google.com744faba2012-05-29 19:54:52 +0000855void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
856 SkDEBUGCODE(this->validate();)
857 if (count <= 0) {
858 return;
859 }
860
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 SkPathRef::Editor ed(&fPathRef);
862 fLastMoveToIndex = ed.pathRef()->countPoints();
863 uint8_t* vb;
864 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000865 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000868 memcpy(p, pts, count * sizeof(SkPoint));
869 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000870 if (count > 1) {
871 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
872 // be 0, the compiler will remove the test/branch entirely.
873 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000875 } else {
876 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000878 }
879 }
880 fSegmentMask |= kLine_SegmentMask;
881 }
882 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000884 }
885
reed@google.com744faba2012-05-29 19:54:52 +0000886 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000888}
889
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000890#include "SkGeometry.h"
891
892static int build_arc_points(const SkRect& oval, SkScalar startAngle,
893 SkScalar sweepAngle,
894 SkPoint pts[kSkBuildQuadArcStorage]) {
895
896 if (0 == sweepAngle &&
897 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
898 // Chrome uses this path to move into and out of ovals. If not
899 // treated as a special case the moves can distort the oval's
900 // bounding box (and break the circle special case).
901 pts[0].set(oval.fRight, oval.centerY());
902 return 1;
903 } else if (0 == oval.width() && 0 == oval.height()) {
904 // Chrome will sometimes create 0 radius round rects. Having degenerate
905 // quad segments in the path prevents the path from being recognized as
906 // a rect.
907 // TODO: optimizing the case where only one of width or height is zero
908 // should also be considered. This case, however, doesn't seem to be
909 // as common as the single point case.
910 pts[0].set(oval.fRight, oval.fTop);
911 return 1;
912 }
913
914 SkVector start, stop;
915
916 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
917 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
918 &stop.fX);
919
920 /* If the sweep angle is nearly (but less than) 360, then due to precision
921 loss in radians-conversion and/or sin/cos, we may end up with coincident
922 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
923 of drawing a nearly complete circle (good).
924 e.g. canvas.drawArc(0, 359.99, ...)
925 -vs- canvas.drawArc(0, 359.9, ...)
926 We try to detect this edge case, and tweak the stop vector
927 */
928 if (start == stop) {
929 SkScalar sw = SkScalarAbs(sweepAngle);
930 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
931 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
932 // make a guess at a tiny angle (in radians) to tweak by
933 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
934 // not sure how much will be enough, so we use a loop
935 do {
936 stopRad -= deltaRad;
937 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
938 } while (start == stop);
939 }
940 }
941
942 SkMatrix matrix;
943
944 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
945 matrix.postTranslate(oval.centerX(), oval.centerY());
946
947 return SkBuildQuadArc(start, stop,
948 sweepAngle > 0 ? kCW_SkRotationDirection :
949 kCCW_SkRotationDirection,
950 &matrix, pts);
951}
952
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000953void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000955 SkRRect rrect;
956 rrect.setRectRadii(rect, (const SkVector*) radii);
957 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958}
959
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000960/* The inline clockwise and counterclockwise round rect quad approximations
961 make it easier to see the symmetry patterns used by add corner quads.
962Clockwise corner value
963 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
964 path->quadTo(rect.fLeft, rect.fTop + offPtY,
965 rect.fLeft + midPtX, rect.fTop + midPtY);
966 path->quadTo(rect.fLeft + offPtX, rect.fTop,
967 rect.fLeft + rx, rect.fTop);
968
969 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
970 path->quadTo(rect.fRight - offPtX, rect.fTop,
971 rect.fRight - midPtX, rect.fTop + midPtY);
972 path->quadTo(rect.fRight, rect.fTop + offPtY,
973 rect.fRight, rect.fTop + ry);
974
975 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
976 path->quadTo(rect.fRight, rect.fBottom - offPtY,
977 rect.fRight - midPtX, rect.fBottom - midPtY);
978 path->quadTo(rect.fRight - offPtX, rect.fBottom,
979 rect.fRight - rx, rect.fBottom);
980
981 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
982 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
983 rect.fLeft + midPtX, rect.fBottom - midPtY);
984 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
985 rect.fLeft, rect.fBottom - ry);
986
987Counterclockwise
988 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
989 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
990 rect.fLeft + midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
992 rect.fLeft + rx, rect.fBottom);
993
994 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
995 path->quadTo(rect.fRight - offPtX, rect.fBottom,
996 rect.fRight - midPtX, rect.fBottom - midPtY);
997 path->quadTo(rect.fRight, rect.fBottom - offPtY,
998 rect.fRight, rect.fBottom - ry);
999
1000 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1001 path->quadTo(rect.fRight, rect.fTop + offPtY,
1002 rect.fRight - midPtX, rect.fTop + midPtY);
1003 path->quadTo(rect.fRight - offPtX, rect.fTop,
1004 rect.fRight - rx, rect.fTop);
1005
1006 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1007 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1008 rect.fLeft + midPtX, rect.fTop + midPtY);
1009 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1010 rect.fLeft, rect.fTop + ry);
1011*/
1012static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1013 SkRRect::Corner corner, SkPath::Direction dir) {
1014 const SkRect& rect = rrect.rect();
1015 const SkVector& radii = rrect.radii(corner);
1016 SkScalar rx = radii.fX;
1017 SkScalar ry = radii.fY;
1018 // The mid point of the quadratic arc approximation is half way between the two
1019 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001020 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1021 SkScalar midPtX = rx * mid;
1022 SkScalar midPtY = ry * mid;
1023 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1024 SkScalar offPtX = rx * control;
1025 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001026 static const int kCornerPts = 5;
1027 SkScalar xOff[kCornerPts];
1028 SkScalar yOff[kCornerPts];
1029
1030 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1031 SkASSERT(dir == SkPath::kCCW_Direction
1032 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1033 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1034 xOff[0] = xOff[1] = 0;
1035 xOff[2] = midPtX;
1036 xOff[3] = offPtX;
1037 xOff[4] = rx;
1038 yOff[0] = ry;
1039 yOff[1] = offPtY;
1040 yOff[2] = midPtY;
1041 yOff[3] = yOff[4] = 0;
1042 } else {
1043 xOff[0] = rx;
1044 xOff[1] = offPtX;
1045 xOff[2] = midPtX;
1046 xOff[3] = xOff[4] = 0;
1047 yOff[0] = yOff[1] = 0;
1048 yOff[2] = midPtY;
1049 yOff[3] = offPtY;
1050 yOff[4] = ry;
1051 }
1052 if ((corner - 1) & 2) {
1053 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1054 for (int i = 0; i < kCornerPts; ++i) {
1055 xOff[i] = rect.fLeft + xOff[i];
1056 }
1057 } else {
1058 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1059 for (int i = 0; i < kCornerPts; ++i) {
1060 xOff[i] = rect.fRight - xOff[i];
1061 }
1062 }
1063 if (corner < SkRRect::kLowerRight_Corner) {
1064 for (int i = 0; i < kCornerPts; ++i) {
1065 yOff[i] = rect.fTop + yOff[i];
1066 }
1067 } else {
1068 for (int i = 0; i < kCornerPts; ++i) {
1069 yOff[i] = rect.fBottom - yOff[i];
1070 }
1071 }
1072
1073 SkPoint lastPt;
1074 SkAssertResult(path->getLastPt(&lastPt));
1075 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1076 path->lineTo(xOff[0], yOff[0]);
1077 }
1078 if (rx || ry) {
1079 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1080 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1081 } else {
1082 path->lineTo(xOff[2], yOff[2]);
1083 path->lineTo(xOff[4], yOff[4]);
1084 }
1085}
1086
reed@google.com4ed0fb72012-12-12 20:48:18 +00001087void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001088 assert_known_direction(dir);
1089
1090 if (rrect.isEmpty()) {
1091 return;
1092 }
1093
reed@google.com4ed0fb72012-12-12 20:48:18 +00001094 const SkRect& bounds = rrect.getBounds();
1095
1096 if (rrect.isRect()) {
1097 this->addRect(bounds, dir);
1098 } else if (rrect.isOval()) {
1099 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001100#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001101 } else if (rrect.isSimple()) {
1102 const SkVector& rad = rrect.getSimpleRadii();
1103 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001104#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001105 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001106 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001107
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001108 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001109 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001110
1111 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001112 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001113 this->moveTo(bounds.fLeft,
1114 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1115 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1116 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1117 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1118 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001120 this->moveTo(bounds.fLeft,
1121 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1122 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1123 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1124 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1125 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001126 }
1127 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001128 }
1129}
1130
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001131bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001132 int count = fPathRef->countVerbs();
1133 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1134 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001135 if (*verbs == kLine_Verb ||
1136 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001137 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001138 *verbs == kCubic_Verb) {
1139 return false;
1140 }
1141 ++verbs;
1142 }
1143 return true;
1144}
1145
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001146#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001148#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001149
1150void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1151 Direction dir) {
1152 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001153
humper@google.com75e3ca12013-04-08 21:44:11 +00001154 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001155 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001156 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001157 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001158 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1159 return;
1160 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001161
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001162#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001163 SkScalar w = rect.width();
1164 SkScalar halfW = SkScalarHalf(w);
1165 SkScalar h = rect.height();
1166 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001167
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001168 if (halfW <= 0 || halfH <= 0) {
1169 return;
1170 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001171
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 bool skip_hori = rx >= halfW;
1173 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001174
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001175 if (skip_hori && skip_vert) {
1176 this->addOval(rect, dir);
1177 return;
1178 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001179
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001180 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001181
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001182 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001183 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001184
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001185 if (skip_hori) {
1186 rx = halfW;
1187 } else if (skip_vert) {
1188 ry = halfH;
1189 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001190 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1191 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001192
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001193 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001194 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001195 if (dir == kCCW_Direction) {
1196 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 }
1199 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1200 rect.fLeft, rect.fTop + ry - sy,
1201 rect.fLeft, rect.fTop + ry); // top-left
1202 if (!skip_vert) {
1203 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1204 }
1205 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1206 rect.fLeft + rx - sx, rect.fBottom,
1207 rect.fLeft + rx, rect.fBottom); // bot-left
1208 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001209 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001210 }
1211 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1212 rect.fRight, rect.fBottom - ry + sy,
1213 rect.fRight, rect.fBottom - ry); // bot-right
1214 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001215 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001216 }
1217 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1218 rect.fRight - rx + sx, rect.fTop,
1219 rect.fRight - rx, rect.fTop); // top-right
1220 } else {
1221 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1222 rect.fRight, rect.fTop + ry - sy,
1223 rect.fRight, rect.fTop + ry); // top-right
1224 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001225 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001226 }
1227 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1228 rect.fRight - rx + sx, rect.fBottom,
1229 rect.fRight - rx, rect.fBottom); // bot-right
1230 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001231 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001232 }
1233 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1234 rect.fLeft, rect.fBottom - ry + sy,
1235 rect.fLeft, rect.fBottom - ry); // bot-left
1236 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001237 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001238 }
1239 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1240 rect.fLeft + rx - sx, rect.fTop,
1241 rect.fLeft + rx, rect.fTop); // top-left
1242 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001243 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001244 }
1245 }
1246 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001247#else
1248 SkRRect rrect;
1249 rrect.setRectXY(rect, rx, ry);
1250 this->addRRect(rrect, dir);
1251#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001252}
1253
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001255 assert_known_direction(dir);
1256
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001257 /* If addOval() is called after previous moveTo(),
1258 this path is still marked as an oval. This is used to
1259 fit into WebKit's calling sequences.
1260 We can't simply check isEmpty() in this case, as additional
1261 moveTo() would mark the path non empty.
1262 */
1263 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001264 if (fIsOval) {
1265 fDirection = dir;
1266 } else {
1267 fDirection = kUnknown_Direction;
1268 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001269
1270 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001271 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 SkAutoPathBoundsUpdate apbu(this, oval);
1274
1275 SkScalar cx = oval.centerX();
1276 SkScalar cy = oval.centerY();
1277 SkScalar rx = SkScalarHalf(oval.width());
1278 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1281 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1282 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1283 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1284
1285 /*
1286 To handle imprecision in computing the center and radii, we revert to
1287 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1288 to ensure that we don't exceed the oval's bounds *ever*, since we want
1289 to use oval for our fast-bounds, rather than have to recompute it.
1290 */
1291 const SkScalar L = oval.fLeft; // cx - rx
1292 const SkScalar T = oval.fTop; // cy - ry
1293 const SkScalar R = oval.fRight; // cx + rx
1294 const SkScalar B = oval.fBottom; // cy + ry
1295
1296 this->incReserve(17); // 8 quads + close
1297 this->moveTo(R, cy);
1298 if (dir == kCCW_Direction) {
1299 this->quadTo( R, cy - sy, cx + mx, cy - my);
1300 this->quadTo(cx + sx, T, cx , T);
1301 this->quadTo(cx - sx, T, cx - mx, cy - my);
1302 this->quadTo( L, cy - sy, L, cy );
1303 this->quadTo( L, cy + sy, cx - mx, cy + my);
1304 this->quadTo(cx - sx, B, cx , B);
1305 this->quadTo(cx + sx, B, cx + mx, cy + my);
1306 this->quadTo( R, cy + sy, R, cy );
1307 } else {
1308 this->quadTo( R, cy + sy, cx + mx, cy + my);
1309 this->quadTo(cx + sx, B, cx , B);
1310 this->quadTo(cx - sx, B, cx - mx, cy + my);
1311 this->quadTo( L, cy + sy, L, cy );
1312 this->quadTo( L, cy - sy, cx - mx, cy - my);
1313 this->quadTo(cx - sx, T, cx , T);
1314 this->quadTo(cx + sx, T, cx + mx, cy - my);
1315 this->quadTo( R, cy - sy, R, cy );
1316 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 this->close();
1318}
1319
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001320bool SkPath::isOval(SkRect* rect) const {
1321 if (fIsOval && rect) {
1322 *rect = getBounds();
1323 }
1324
1325 return fIsOval;
1326}
1327
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1329 if (r > 0) {
1330 SkRect rect;
1331 rect.set(x - r, y - r, x + r, y + r);
1332 this->addOval(rect, dir);
1333 }
1334}
1335
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1337 bool forceMoveTo) {
1338 if (oval.width() < 0 || oval.height() < 0) {
1339 return;
1340 }
1341
1342 SkPoint pts[kSkBuildQuadArcStorage];
1343 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1344 SkASSERT((count & 1) == 1);
1345
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001346 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 forceMoveTo = true;
1348 }
1349 this->incReserve(count);
1350 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1351 for (int i = 1; i < count; i += 2) {
1352 this->quadTo(pts[i], pts[i+1]);
1353 }
1354}
1355
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001356void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 if (oval.isEmpty() || 0 == sweepAngle) {
1358 return;
1359 }
1360
1361 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1362
1363 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1364 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1365 return;
1366 }
1367
1368 SkPoint pts[kSkBuildQuadArcStorage];
1369 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1370
1371 this->incReserve(count);
1372 this->moveTo(pts[0]);
1373 for (int i = 1; i < count; i += 2) {
1374 this->quadTo(pts[i], pts[i+1]);
1375 }
1376}
1377
1378/*
1379 Need to handle the case when the angle is sharp, and our computed end-points
1380 for the arc go behind pt1 and/or p2...
1381*/
1382void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1383 SkScalar radius) {
1384 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001385
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 // need to know our prev pt so we can construct tangent vectors
1387 {
1388 SkPoint start;
1389 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001390 // Handle degenerate cases by adding a line to the first point and
1391 // bailing out.
1392 if ((x1 == start.fX && y1 == start.fY) ||
1393 (x1 == x2 && y1 == y2) ||
1394 radius == 0) {
1395 this->lineTo(x1, y1);
1396 return;
1397 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 before.setNormalize(x1 - start.fX, y1 - start.fY);
1399 after.setNormalize(x2 - x1, y2 - y1);
1400 }
reed@google.comabf15c12011-01-18 20:35:51 +00001401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 SkScalar cosh = SkPoint::DotProduct(before, after);
1403 SkScalar sinh = SkPoint::CrossProduct(before, after);
1404
1405 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001406 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 return;
1408 }
reed@google.comabf15c12011-01-18 20:35:51 +00001409
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1411 if (dist < 0) {
1412 dist = -dist;
1413 }
1414
1415 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1416 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1417 SkRotationDirection arcDir;
1418
1419 // now turn before/after into normals
1420 if (sinh > 0) {
1421 before.rotateCCW();
1422 after.rotateCCW();
1423 arcDir = kCW_SkRotationDirection;
1424 } else {
1425 before.rotateCW();
1426 after.rotateCW();
1427 arcDir = kCCW_SkRotationDirection;
1428 }
1429
1430 SkMatrix matrix;
1431 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001432
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 matrix.setScale(radius, radius);
1434 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1435 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001436
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001438
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 this->incReserve(count);
1440 // [xx,yy] == pts[0]
1441 this->lineTo(xx, yy);
1442 for (int i = 1; i < count; i += 2) {
1443 this->quadTo(pts[i], pts[i+1]);
1444 }
1445}
1446
1447///////////////////////////////////////////////////////////////////////////////
1448
1449void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1450 SkMatrix matrix;
1451
1452 matrix.setTranslate(dx, dy);
1453 this->addPath(path, matrix);
1454}
1455
1456void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001457 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001459 fIsOval = false;
1460
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001461 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 SkPoint pts[4];
1463 Verb verb;
1464
1465 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1466
1467 while ((verb = iter.next(pts)) != kDone_Verb) {
1468 switch (verb) {
1469 case kMove_Verb:
1470 proc(matrix, &pts[0], &pts[0], 1);
1471 this->moveTo(pts[0]);
1472 break;
1473 case kLine_Verb:
1474 proc(matrix, &pts[1], &pts[1], 1);
1475 this->lineTo(pts[1]);
1476 break;
1477 case kQuad_Verb:
1478 proc(matrix, &pts[1], &pts[1], 2);
1479 this->quadTo(pts[1], pts[2]);
1480 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001481 case kConic_Verb:
1482 proc(matrix, &pts[1], &pts[1], 2);
1483 this->conicTo(pts[1], pts[2], iter.conicWeight());
1484 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 case kCubic_Verb:
1486 proc(matrix, &pts[1], &pts[1], 3);
1487 this->cubicTo(pts[1], pts[2], pts[3]);
1488 break;
1489 case kClose_Verb:
1490 this->close();
1491 break;
1492 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001493 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 }
1495 }
1496}
1497
1498///////////////////////////////////////////////////////////////////////////////
1499
reed@google.com277c3f82013-05-31 15:17:50 +00001500static int pts_in_verb(unsigned verb) {
1501 static const uint8_t gPtsInVerb[] = {
1502 1, // kMove
1503 1, // kLine
1504 2, // kQuad
1505 2, // kConic
1506 3, // kCubic
1507 0, // kClose
1508 0 // kDone
1509 };
1510
1511 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1512 return gPtsInVerb[verb];
1513}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515// ignore the last point of the 1st contour
1516void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 int i, vcount = path.fPathRef->countVerbs();
1518 // exit early if the path is empty, or just has a moveTo.
1519 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 return;
1521 }
1522
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001525 fIsOval = false;
1526
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 const uint8_t* verbs = path.fPathRef->verbs();
1528 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001529 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 SkASSERT(verbs[~0] == kMove_Verb);
1532 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001533 unsigned v = verbs[~i];
1534 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 if (n == 0) {
1536 break;
1537 }
1538 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001539 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 }
1541
1542 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001543 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 case kLine_Verb:
1545 this->lineTo(pts[-1].fX, pts[-1].fY);
1546 break;
1547 case kQuad_Verb:
1548 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1549 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001550 case kConic_Verb:
1551 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1552 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 case kCubic_Verb:
1554 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1555 pts[-3].fX, pts[-3].fY);
1556 break;
1557 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001558 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 break;
1560 }
reed@google.com277c3f82013-05-31 15:17:50 +00001561 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 }
1563}
1564
reed@google.com63d73742012-01-10 15:33:12 +00001565void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001566 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 const SkPoint* pts = src.fPathRef->pointsEnd();
1569 // we will iterator through src's verbs backwards
1570 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1571 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001572 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001573
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001574 fIsOval = false;
1575
reed@google.com63d73742012-01-10 15:33:12 +00001576 bool needMove = true;
1577 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001578 while (verbs < verbsEnd) {
1579 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001580 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001581
1582 if (needMove) {
1583 --pts;
1584 this->moveTo(pts->fX, pts->fY);
1585 needMove = false;
1586 }
1587 pts -= n;
1588 switch (v) {
1589 case kMove_Verb:
1590 if (needClose) {
1591 this->close();
1592 needClose = false;
1593 }
1594 needMove = true;
1595 pts += 1; // so we see the point in "if (needMove)" above
1596 break;
1597 case kLine_Verb:
1598 this->lineTo(pts[0]);
1599 break;
1600 case kQuad_Verb:
1601 this->quadTo(pts[1], pts[0]);
1602 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001603 case kConic_Verb:
1604 this->conicTo(pts[1], pts[0], *--conicWeights);
1605 break;
reed@google.com63d73742012-01-10 15:33:12 +00001606 case kCubic_Verb:
1607 this->cubicTo(pts[2], pts[1], pts[0]);
1608 break;
1609 case kClose_Verb:
1610 needClose = true;
1611 break;
1612 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001613 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001614 }
1615 }
1616}
1617
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618///////////////////////////////////////////////////////////////////////////////
1619
1620void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1621 SkMatrix matrix;
1622
1623 matrix.setTranslate(dx, dy);
1624 this->transform(matrix, dst);
1625}
1626
1627#include "SkGeometry.h"
1628
1629static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1630 int level = 2) {
1631 if (--level >= 0) {
1632 SkPoint tmp[5];
1633
1634 SkChopQuadAtHalf(pts, tmp);
1635 subdivide_quad_to(path, &tmp[0], level);
1636 subdivide_quad_to(path, &tmp[2], level);
1637 } else {
1638 path->quadTo(pts[1], pts[2]);
1639 }
1640}
1641
1642static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1643 int level = 2) {
1644 if (--level >= 0) {
1645 SkPoint tmp[7];
1646
1647 SkChopCubicAtHalf(pts, tmp);
1648 subdivide_cubic_to(path, &tmp[0], level);
1649 subdivide_cubic_to(path, &tmp[3], level);
1650 } else {
1651 path->cubicTo(pts[1], pts[2], pts[3]);
1652 }
1653}
1654
1655void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1656 SkDEBUGCODE(this->validate();)
1657 if (dst == NULL) {
1658 dst = (SkPath*)this;
1659 }
1660
tomhudson@google.com8d430182011-06-06 19:11:19 +00001661 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 SkPath tmp;
1663 tmp.fFillType = fFillType;
1664
1665 SkPath::Iter iter(*this, false);
1666 SkPoint pts[4];
1667 SkPath::Verb verb;
1668
reed@google.com4a3b7142012-05-16 17:16:46 +00001669 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 switch (verb) {
1671 case kMove_Verb:
1672 tmp.moveTo(pts[0]);
1673 break;
1674 case kLine_Verb:
1675 tmp.lineTo(pts[1]);
1676 break;
1677 case kQuad_Verb:
1678 subdivide_quad_to(&tmp, pts);
1679 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001680 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001681 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001682 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1683 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 case kCubic_Verb:
1685 subdivide_cubic_to(&tmp, pts);
1686 break;
1687 case kClose_Verb:
1688 tmp.close();
1689 break;
1690 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001691 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 break;
1693 }
1694 }
1695
1696 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001697 SkPathRef::Editor ed(&dst->fPathRef);
1698 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001699 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001701 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1702
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001705 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001706 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001708
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001709 if (kUnknown_Direction == fDirection) {
1710 dst->fDirection = kUnknown_Direction;
1711 } else {
1712 SkScalar det2x2 =
1713 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1714 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1715 if (det2x2 < 0) {
1716 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1717 } else if (det2x2 > 0) {
1718 dst->fDirection = fDirection;
1719 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001720 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001721 dst->fDirection = kUnknown_Direction;
1722 }
1723 }
1724
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001725 // It's an oval only if it stays a rect.
1726 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001727
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 SkDEBUGCODE(dst->validate();)
1729 }
1730}
1731
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732///////////////////////////////////////////////////////////////////////////////
1733///////////////////////////////////////////////////////////////////////////////
1734
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001736 kEmptyContour_SegmentState, // The current contour is empty. We may be
1737 // starting processing or we may have just
1738 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1740 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1741 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742};
1743
1744SkPath::Iter::Iter() {
1745#ifdef SK_DEBUG
1746 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001747 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001749 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001750 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751#endif
1752 // need to init enough to make next() harmlessly return kDone_Verb
1753 fVerbs = NULL;
1754 fVerbStop = NULL;
1755 fNeedClose = false;
1756}
1757
1758SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1759 this->setPath(path, forceClose);
1760}
1761
1762void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 fPts = path.fPathRef->points();
1764 fVerbs = path.fPathRef->verbs();
1765 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001766 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001768 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 fForceClose = SkToU8(forceClose);
1770 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001771 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772}
1773
1774bool SkPath::Iter::isClosedContour() const {
1775 if (fVerbs == NULL || fVerbs == fVerbStop) {
1776 return false;
1777 }
1778 if (fForceClose) {
1779 return true;
1780 }
1781
1782 const uint8_t* verbs = fVerbs;
1783 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001785 if (kMove_Verb == *(verbs - 1)) {
1786 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 }
1788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 while (verbs > stop) {
1790 // verbs points one beyond the current verb, decrement first.
1791 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 if (kMove_Verb == v) {
1793 break;
1794 }
1795 if (kClose_Verb == v) {
1796 return true;
1797 }
1798 }
1799 return false;
1800}
1801
1802SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001805 // A special case: if both points are NaN, SkPoint::operation== returns
1806 // false, but the iterator expects that they are treated as the same.
1807 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001808 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1809 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001810 return kClose_Verb;
1811 }
1812
reed@google.com9e25dbf2012-05-15 17:05:38 +00001813 pts[0] = fLastPt;
1814 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fLastPt = fMoveTo;
1816 fCloseLine = true;
1817 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001818 } else {
1819 pts[0] = fMoveTo;
1820 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822}
1823
reed@google.com9e25dbf2012-05-15 17:05:38 +00001824const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 if (fSegmentState == kAfterMove_SegmentState) {
1826 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001828 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1831 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834}
1835
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001836void SkPath::Iter::consumeDegenerateSegments() {
1837 // We need to step over anything that will not move the current draw point
1838 // forward before the next move is seen
1839 const uint8_t* lastMoveVerb = 0;
1840 const SkPoint* lastMovePt = 0;
1841 SkPoint lastPt = fLastPt;
1842 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 switch (verb) {
1845 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 // Keep a record of this most recent move
1847 lastMoveVerb = fVerbs;
1848 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001849 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 fPts++;
1852 break;
1853
1854 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001855 // A close when we are in a segment is always valid except when it
1856 // follows a move which follows a segment.
1857 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 return;
1859 }
1860 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001861 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 break;
1863
1864 case kLine_Verb:
1865 if (!IsLineDegenerate(lastPt, fPts[0])) {
1866 if (lastMoveVerb) {
1867 fVerbs = lastMoveVerb;
1868 fPts = lastMovePt;
1869 return;
1870 }
1871 return;
1872 }
1873 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 fPts++;
1876 break;
1877
reed@google.com277c3f82013-05-31 15:17:50 +00001878 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 case kQuad_Verb:
1880 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1881 if (lastMoveVerb) {
1882 fVerbs = lastMoveVerb;
1883 fPts = lastMovePt;
1884 return;
1885 }
1886 return;
1887 }
1888 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001889 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001891 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 break;
1893
1894 case kCubic_Verb:
1895 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1896 if (lastMoveVerb) {
1897 fVerbs = lastMoveVerb;
1898 fPts = lastMovePt;
1899 return;
1900 }
1901 return;
1902 }
1903 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 fPts += 3;
1906 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001907
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001909 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 }
1911 }
1912}
1913
reed@google.com4a3b7142012-05-16 17:16:46 +00001914SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 // Close the curve if requested and if there is some curve to close
1919 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001920 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 return kLine_Verb;
1922 }
1923 fNeedClose = false;
1924 return kClose_Verb;
1925 }
1926 return kDone_Verb;
1927 }
1928
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001929 // fVerbs is one beyond the current verb, decrement first
1930 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 const SkPoint* SK_RESTRICT srcPts = fPts;
1932 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933
1934 switch (verb) {
1935 case kMove_Verb:
1936 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 verb = this->autoClose(pts);
1939 if (verb == kClose_Verb) {
1940 fNeedClose = false;
1941 }
1942 return (Verb)verb;
1943 }
1944 if (fVerbs == fVerbStop) { // might be a trailing moveto
1945 return kDone_Verb;
1946 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001947 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001950 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 fNeedClose = fForceClose;
1953 break;
1954 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 pts[0] = this->cons_moveTo();
1956 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 fLastPt = srcPts[0];
1958 fCloseLine = false;
1959 srcPts += 1;
1960 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001961 case kConic_Verb:
1962 fConicWeights += 1;
1963 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001965 pts[0] = this->cons_moveTo();
1966 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 fLastPt = srcPts[1];
1968 srcPts += 2;
1969 break;
1970 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001971 pts[0] = this->cons_moveTo();
1972 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 fLastPt = srcPts[2];
1974 srcPts += 3;
1975 break;
1976 case kClose_Verb:
1977 verb = this->autoClose(pts);
1978 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 } else {
1981 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001982 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 break;
1986 }
1987 fPts = srcPts;
1988 return (Verb)verb;
1989}
1990
1991///////////////////////////////////////////////////////////////////////////////
1992
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001993SkPath::RawIter::RawIter() {
1994#ifdef SK_DEBUG
1995 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001996 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001997 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1998#endif
1999 // need to init enough to make next() harmlessly return kDone_Verb
2000 fVerbs = NULL;
2001 fVerbStop = NULL;
2002}
2003
2004SkPath::RawIter::RawIter(const SkPath& path) {
2005 this->setPath(path);
2006}
2007
2008void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002009 fPts = path.fPathRef->points();
2010 fVerbs = path.fPathRef->verbs();
2011 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002012 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002013 fMoveTo.fX = fMoveTo.fY = 0;
2014 fLastPt.fX = fLastPt.fY = 0;
2015}
2016
2017SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002018 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019 if (fVerbs == fVerbStop) {
2020 return kDone_Verb;
2021 }
2022
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002023 // fVerbs points one beyond next verb so decrement first.
2024 unsigned verb = *(--fVerbs);
2025 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002026
2027 switch (verb) {
2028 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002029 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 fMoveTo = srcPts[0];
2031 fLastPt = fMoveTo;
2032 srcPts += 1;
2033 break;
2034 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002035 pts[0] = fLastPt;
2036 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 fLastPt = srcPts[0];
2038 srcPts += 1;
2039 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002040 case kConic_Verb:
2041 fConicWeights += 1;
2042 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002043 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002044 pts[0] = fLastPt;
2045 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002046 fLastPt = srcPts[1];
2047 srcPts += 2;
2048 break;
2049 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002050 pts[0] = fLastPt;
2051 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002052 fLastPt = srcPts[2];
2053 srcPts += 3;
2054 break;
2055 case kClose_Verb:
2056 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002057 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002058 break;
2059 }
2060 fPts = srcPts;
2061 return (Verb)verb;
2062}
2063
2064///////////////////////////////////////////////////////////////////////////////
2065
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002067 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068*/
2069
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002070size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071 SkDEBUGCODE(this->validate();)
2072
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002073 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002074 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002075 return SkAlign4(byteCount);
2076 }
2077
2078 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002079
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002080 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081 (fConvexity << kConvexity_SerializationShift) |
2082 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002083 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002084 (fDirection << kDirection_SerializationShift)
2085#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002086 | (0x1 << kNewFormat_SerializationShift)
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002087#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002088 ;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002090 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002091
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002092 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002093
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002094 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002095 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096}
2097
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002098size_t SkPath::readFromMemory(const void* storage, size_t length) {
2099 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002100
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002101 int32_t packed;
2102 if (!buffer.readS32(&packed)) {
2103 return 0;
2104 }
2105
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002106 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2107 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2108 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002109 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002110 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002111#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2112 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2113#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002115 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002116#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002117 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002118#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002119 );
reed@google.comabf15c12011-01-18 20:35:51 +00002120
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002121 size_t sizeRead = 0;
2122 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002123 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002124 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002125 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002126 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002127 } else if (NULL != pathRef) {
2128 // If the buffer is not valid, pathRef should be NULL
2129 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002130 }
2131 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132}
2133
2134///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135
reed@google.com51bbe752013-01-17 22:07:50 +00002136#include "SkString.h"
2137
2138static void append_scalar(SkString* str, SkScalar value) {
2139 SkString tmp;
2140 tmp.printf("%g", value);
2141 if (tmp.contains('.')) {
2142 tmp.appendUnichar('f');
2143 }
2144 str->append(tmp);
2145}
2146
2147static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002148 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002149 str->append(label);
2150 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002151
reed@google.com51bbe752013-01-17 22:07:50 +00002152 const SkScalar* values = &pts[0].fX;
2153 count *= 2;
2154
2155 for (int i = 0; i < count; ++i) {
2156 append_scalar(str, values[i]);
2157 if (i < count - 1) {
2158 str->append(", ");
2159 }
2160 }
reed@google.com277c3f82013-05-31 15:17:50 +00002161 if (conicWeight >= 0) {
2162 str->append(", ");
2163 append_scalar(str, conicWeight);
2164 }
reed@google.com51bbe752013-01-17 22:07:50 +00002165 str->append(");\n");
2166}
2167
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168void SkPath::dump(bool forceClose, const char title[]) const {
2169 Iter iter(*this, forceClose);
2170 SkPoint pts[4];
2171 Verb verb;
2172
2173 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2174 title ? title : "");
2175
reed@google.com51bbe752013-01-17 22:07:50 +00002176 SkString builder;
2177
reed@google.com4a3b7142012-05-16 17:16:46 +00002178 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 switch (verb) {
2180 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002181 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182 break;
2183 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002184 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 break;
2186 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002187 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002189 case kConic_Verb:
2190 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2191 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002193 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 break;
2195 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002196 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 break;
2198 default:
2199 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2200 verb = kDone_Verb; // stop the loop
2201 break;
2202 }
2203 }
reed@google.com51bbe752013-01-17 22:07:50 +00002204 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002205}
2206
reed@android.come522ca52009-11-23 20:10:41 +00002207void SkPath::dump() const {
2208 this->dump(false);
2209}
2210
2211#ifdef SK_DEBUG
2212void SkPath::validate() const {
2213 SkASSERT(this != NULL);
2214 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002215
djsollen@google.com077348c2012-10-22 20:23:32 +00002216#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002217 if (!fBoundsIsDirty) {
2218 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002219
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002220 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002221 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002222
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002223 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002224 // if we're empty, fBounds may be empty but translated, so we can't
2225 // necessarily compare to bounds directly
2226 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2227 // be [2, 2, 2, 2]
2228 SkASSERT(bounds.isEmpty());
2229 SkASSERT(fBounds.isEmpty());
2230 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002231 if (bounds.isEmpty()) {
2232 SkASSERT(fBounds.isEmpty());
2233 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002234 if (!fBounds.isEmpty()) {
2235 SkASSERT(fBounds.contains(bounds));
2236 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002237 }
reed@android.come522ca52009-11-23 20:10:41 +00002238 }
2239 }
reed@google.com10296cc2011-09-21 12:29:05 +00002240
2241 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002242 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2243 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2244 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002245 case kLine_Verb:
2246 mask |= kLine_SegmentMask;
2247 break;
2248 case kQuad_Verb:
2249 mask |= kQuad_SegmentMask;
2250 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002251 case kConic_Verb:
2252 mask |= kConic_SegmentMask;
2253 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002254 case kCubic_Verb:
2255 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002256 case kMove_Verb: // these verbs aren't included in the segment mask.
2257 case kClose_Verb:
2258 break;
2259 case kDone_Verb:
2260 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2261 break;
2262 default:
2263 SkDEBUGFAIL("Unknown Verb");
2264 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002265 }
2266 }
2267 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002268#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002269}
djsollen@google.com077348c2012-10-22 20:23:32 +00002270#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002271
reed@google.com04863fa2011-05-15 04:08:24 +00002272///////////////////////////////////////////////////////////////////////////////
2273
reed@google.com0b7b9822011-05-16 12:29:27 +00002274static int sign(SkScalar x) { return x < 0; }
2275#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002276
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002277static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2278 // The error epsilon was empirically derived; worse case round rects
2279 // with a mid point outset by 2x float epsilon in tests had an error
2280 // of 12.
2281 const int epsilon = 16;
2282 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2283 return false;
2284 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002285 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002286 int aBits = SkFloatAs2sCompliment(compA);
2287 int bBits = SkFloatAs2sCompliment(compB);
2288 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002289}
2290
2291// only valid for a single contour
2292struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002293 Convexicator()
2294 : fPtCount(0)
2295 , fConvexity(SkPath::kConvex_Convexity)
2296 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002297 fSign = 0;
2298 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002299 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002300 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) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002325 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002326 fCurrPt = pt;
2327 if (++fPtCount == 2) {
2328 fFirstVec = fVec1 = vec;
2329 } else {
2330 SkASSERT(fPtCount > 2);
2331 this->addVec(vec);
2332 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002333
reed@google.com85b6e392011-05-15 20:25:17 +00002334 int sx = sign(vec.fX);
2335 int sy = sign(vec.fY);
2336 fDx += (sx != fSx);
2337 fDy += (sy != fSy);
2338 fSx = sx;
2339 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002340
reed@google.com85b6e392011-05-15 20:25:17 +00002341 if (fDx > 3 || fDy > 3) {
2342 fConvexity = SkPath::kConcave_Convexity;
2343 }
reed@google.com04863fa2011-05-15 04:08:24 +00002344 }
2345 }
2346 }
2347
2348 void close() {
2349 if (fPtCount > 2) {
2350 this->addVec(fFirstVec);
2351 }
2352 }
2353
2354private:
2355 void addVec(const SkVector& vec) {
2356 SkASSERT(vec.fX || vec.fY);
2357 fVec0 = fVec1;
2358 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002359 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2360 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2361 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2362 largest = SkTMax(largest, -smallest);
2363 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002364 if (0 == fSign) {
2365 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002366 if (1 == sign) {
2367 fDirection = SkPath::kCW_Direction;
2368 } else if (-1 == sign) {
2369 fDirection = SkPath::kCCW_Direction;
2370 }
reed@google.com04863fa2011-05-15 04:08:24 +00002371 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002372 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002373 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002374 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 }
2376 }
2377 }
2378
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002379 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002380 SkPoint fCurrPt;
2381 SkVector fVec0, fVec1, fFirstVec;
2382 int fPtCount; // non-degenerate points
2383 int fSign;
2384 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002386 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002387};
2388
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389SkPath::Convexity SkPath::internalGetConvexity() const {
2390 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002391 SkPoint pts[4];
2392 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002393 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002394
2395 int contourCount = 0;
2396 int count;
2397 Convexicator state;
2398
2399 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2400 switch (verb) {
2401 case kMove_Verb:
2402 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002403 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 return kConcave_Convexity;
2405 }
2406 pts[1] = pts[0];
2407 count = 1;
2408 break;
2409 case kLine_Verb: count = 1; break;
2410 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002411 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 case kCubic_Verb: count = 3; break;
2413 case kClose_Verb:
2414 state.close();
2415 count = 0;
2416 break;
2417 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002418 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002419 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 return kConcave_Convexity;
2421 }
2422
2423 for (int i = 1; i <= count; i++) {
2424 state.addPt(pts[i]);
2425 }
2426 // early exit
2427 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002429 return kConcave_Convexity;
2430 }
2431 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002432 fConvexity = state.getConvexity();
2433 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2434 fDirection = state.getDirection();
2435 }
2436 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002437}
reed@google.com69a99432012-01-10 18:00:10 +00002438
2439///////////////////////////////////////////////////////////////////////////////
2440
2441class ContourIter {
2442public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002443 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002444
2445 bool done() const { return fDone; }
2446 // if !done() then these may be called
2447 int count() const { return fCurrPtCount; }
2448 const SkPoint* pts() const { return fCurrPt; }
2449 void next();
2450
2451private:
2452 int fCurrPtCount;
2453 const SkPoint* fCurrPt;
2454 const uint8_t* fCurrVerb;
2455 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002456 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002457 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002458 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002459};
2460
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002461ContourIter::ContourIter(const SkPathRef& pathRef) {
2462 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002463 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002464 fCurrPt = pathRef.points();
2465 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002466 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002467 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002468 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002469 this->next();
2470}
2471
2472void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002473 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002474 fDone = true;
2475 }
2476 if (fDone) {
2477 return;
2478 }
2479
2480 // skip pts of prev contour
2481 fCurrPt += fCurrPtCount;
2482
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002483 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002484 int ptCount = 1; // moveTo
2485 const uint8_t* verbs = fCurrVerb;
2486
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002487 for (--verbs; verbs > fStopVerbs; --verbs) {
2488 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002489 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002490 goto CONTOUR_END;
2491 case SkPath::kLine_Verb:
2492 ptCount += 1;
2493 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002494 case SkPath::kConic_Verb:
2495 fCurrConicWeight += 1;
2496 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002497 case SkPath::kQuad_Verb:
2498 ptCount += 2;
2499 break;
2500 case SkPath::kCubic_Verb:
2501 ptCount += 3;
2502 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002503 case SkPath::kClose_Verb:
2504 break;
2505 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002506 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002507 break;
2508 }
2509 }
2510CONTOUR_END:
2511 fCurrPtCount = ptCount;
2512 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002513 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002514}
2515
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002516// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002517static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002518 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2519 // We may get 0 when the above subtracts underflow. We expect this to be
2520 // very rare and lazily promote to double.
2521 if (0 == cross) {
2522 double p0x = SkScalarToDouble(p0.fX);
2523 double p0y = SkScalarToDouble(p0.fY);
2524
2525 double p1x = SkScalarToDouble(p1.fX);
2526 double p1y = SkScalarToDouble(p1.fY);
2527
2528 double p2x = SkScalarToDouble(p2.fX);
2529 double p2y = SkScalarToDouble(p2.fY);
2530
2531 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2532 (p1y - p0y) * (p2x - p0x));
2533
2534 }
2535 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002536}
2537
reed@google.comc1ea60a2012-01-31 15:15:36 +00002538// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002539static int find_max_y(const SkPoint pts[], int count) {
2540 SkASSERT(count > 0);
2541 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002542 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002543 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002544 SkScalar y = pts[i].fY;
2545 if (y > max) {
2546 max = y;
2547 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002548 }
2549 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002550 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002551}
2552
reed@google.comcabaf1d2012-01-11 21:03:05 +00002553static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2554 int i = index;
2555 for (;;) {
2556 i = (i + inc) % n;
2557 if (i == index) { // we wrapped around, so abort
2558 break;
2559 }
2560 if (pts[index] != pts[i]) { // found a different point, success!
2561 break;
2562 }
2563 }
2564 return i;
2565}
2566
reed@google.comc1ea60a2012-01-31 15:15:36 +00002567/**
2568 * Starting at index, and moving forward (incrementing), find the xmin and
2569 * xmax of the contiguous points that have the same Y.
2570 */
2571static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2572 int* maxIndexPtr) {
2573 const SkScalar y = pts[index].fY;
2574 SkScalar min = pts[index].fX;
2575 SkScalar max = min;
2576 int minIndex = index;
2577 int maxIndex = index;
2578 for (int i = index + 1; i < n; ++i) {
2579 if (pts[i].fY != y) {
2580 break;
2581 }
2582 SkScalar x = pts[i].fX;
2583 if (x < min) {
2584 min = x;
2585 minIndex = i;
2586 } else if (x > max) {
2587 max = x;
2588 maxIndex = i;
2589 }
2590 }
2591 *maxIndexPtr = maxIndex;
2592 return minIndex;
2593}
2594
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002595static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002596 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002597}
2598
reed@google.comac8543f2012-01-30 20:51:25 +00002599/*
2600 * We loop through all contours, and keep the computed cross-product of the
2601 * contour that contained the global y-max. If we just look at the first
2602 * contour, we may find one that is wound the opposite way (correctly) since
2603 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2604 * that is outer most (or at least has the global y-max) before we can consider
2605 * its cross product.
2606 */
reed@google.com69a99432012-01-10 18:00:10 +00002607bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002608 if (kUnknown_Direction != fDirection) {
2609 *dir = static_cast<Direction>(fDirection);
2610 return true;
2611 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002612
2613 // don't want to pay the cost for computing this if it
2614 // is unknown, so we don't call isConvex()
2615 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2616 SkASSERT(kUnknown_Direction == fDirection);
2617 *dir = static_cast<Direction>(fDirection);
2618 return false;
2619 }
reed@google.com69a99432012-01-10 18:00:10 +00002620
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002621 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002622
reed@google.comac8543f2012-01-30 20:51:25 +00002623 // initialize with our logical y-min
2624 SkScalar ymax = this->getBounds().fTop;
2625 SkScalar ymaxCross = 0;
2626
reed@google.com69a99432012-01-10 18:00:10 +00002627 for (; !iter.done(); iter.next()) {
2628 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002629 if (n < 3) {
2630 continue;
2631 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002632
reed@google.comcabaf1d2012-01-11 21:03:05 +00002633 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002634 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002635 int index = find_max_y(pts, n);
2636 if (pts[index].fY < ymax) {
2637 continue;
2638 }
2639
2640 // If there is more than 1 distinct point at the y-max, we take the
2641 // x-min and x-max of them and just subtract to compute the dir.
2642 if (pts[(index + 1) % n].fY == pts[index].fY) {
2643 int maxIndex;
2644 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2645 if (minIndex == maxIndex) {
2646 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002647 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002648 SkASSERT(pts[minIndex].fY == pts[index].fY);
2649 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2650 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2651 // we just subtract the indices, and let that auto-convert to
2652 // SkScalar, since we just want - or + to signal the direction.
2653 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002654 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002655 TRY_CROSSPROD:
2656 // Find a next and prev index to use for the cross-product test,
2657 // but we try to find pts that form non-zero vectors from pts[index]
2658 //
2659 // Its possible that we can't find two non-degenerate vectors, so
2660 // we have to guard our search (e.g. all the pts could be in the
2661 // same place).
2662
2663 // we pass n - 1 instead of -1 so we don't foul up % operator by
2664 // passing it a negative LH argument.
2665 int prev = find_diff_pt(pts, index, n, n - 1);
2666 if (prev == index) {
2667 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002668 continue;
2669 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002670 int next = find_diff_pt(pts, index, n, 1);
2671 SkASSERT(next != index);
2672 cross = cross_prod(pts[prev], pts[index], pts[next]);
2673 // if we get a zero and the points are horizontal, then we look at the spread in
2674 // x-direction. We really should continue to walk away from the degeneracy until
2675 // there is a divergence.
2676 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2677 // construct the subtract so we get the correct Direction below
2678 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002679 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002680 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002681
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002682 if (cross) {
2683 // record our best guess so far
2684 ymax = pts[index].fY;
2685 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002686 }
2687 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002688 if (ymaxCross) {
2689 crossToDir(ymaxCross, dir);
2690 fDirection = *dir;
2691 return true;
2692 } else {
2693 return false;
2694 }
reed@google.comac8543f2012-01-30 20:51:25 +00002695}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002696
2697///////////////////////////////////////////////////////////////////////////////
2698
2699static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2700 SkScalar D, SkScalar t) {
2701 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2702}
2703
2704static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2705 SkScalar t) {
2706 SkScalar A = c3 + 3*(c1 - c2) - c0;
2707 SkScalar B = 3*(c2 - c1 - c1 + c0);
2708 SkScalar C = 3*(c1 - c0);
2709 SkScalar D = c0;
2710 return eval_cubic_coeff(A, B, C, D, t);
2711}
2712
2713/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2714 t value such that cubic(t) = target
2715 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002716static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002717 SkScalar target, SkScalar* t) {
2718 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2719 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002720
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002721 SkScalar D = c0 - target;
2722 SkScalar A = c3 + 3*(c1 - c2) - c0;
2723 SkScalar B = 3*(c2 - c1 - c1 + c0);
2724 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002726 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2727 SkScalar minT = 0;
2728 SkScalar maxT = SK_Scalar1;
2729 SkScalar mid;
2730 int i;
2731 for (i = 0; i < 16; i++) {
2732 mid = SkScalarAve(minT, maxT);
2733 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2734 if (delta < 0) {
2735 minT = mid;
2736 delta = -delta;
2737 } else {
2738 maxT = mid;
2739 }
2740 if (delta < TOLERANCE) {
2741 break;
2742 }
2743 }
2744 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002745}
2746
2747template <size_t N> static void find_minmax(const SkPoint pts[],
2748 SkScalar* minPtr, SkScalar* maxPtr) {
2749 SkScalar min, max;
2750 min = max = pts[0].fX;
2751 for (size_t i = 1; i < N; ++i) {
2752 min = SkMinScalar(min, pts[i].fX);
2753 max = SkMaxScalar(max, pts[i].fX);
2754 }
2755 *minPtr = min;
2756 *maxPtr = max;
2757}
2758
2759static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2760 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 int dir = 1;
2763 if (pts[0].fY > pts[3].fY) {
2764 storage[0] = pts[3];
2765 storage[1] = pts[2];
2766 storage[2] = pts[1];
2767 storage[3] = pts[0];
2768 pts = storage;
2769 dir = -1;
2770 }
2771 if (y < pts[0].fY || y >= pts[3].fY) {
2772 return 0;
2773 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775 // quickreject or quickaccept
2776 SkScalar min, max;
2777 find_minmax<4>(pts, &min, &max);
2778 if (x < min) {
2779 return 0;
2780 }
2781 if (x > max) {
2782 return dir;
2783 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002784
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002786 SkScalar t;
2787 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2788 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002789 return xt < x ? dir : 0;
2790}
2791
2792static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2793 SkPoint dst[10];
2794 int n = SkChopCubicAtYExtrema(pts, dst);
2795 int w = 0;
2796 for (int i = 0; i <= n; ++i) {
2797 w += winding_mono_cubic(&dst[i * 3], x, y);
2798 }
2799 return w;
2800}
2801
2802static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2803 SkScalar y0 = pts[0].fY;
2804 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002805
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 int dir = 1;
2807 if (y0 > y2) {
2808 SkTSwap(y0, y2);
2809 dir = -1;
2810 }
2811 if (y < y0 || y >= y2) {
2812 return 0;
2813 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002814
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002815 // bounds check on X (not required. is it faster?)
2816#if 0
2817 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2818 return 0;
2819 }
2820#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 SkScalar roots[2];
2823 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2824 2 * (pts[1].fY - pts[0].fY),
2825 pts[0].fY - y,
2826 roots);
2827 SkASSERT(n <= 1);
2828 SkScalar xt;
2829 if (0 == n) {
2830 SkScalar mid = SkScalarAve(y0, y2);
2831 // Need [0] and [2] if dir == 1
2832 // and [2] and [0] if dir == -1
2833 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2834 } else {
2835 SkScalar t = roots[0];
2836 SkScalar C = pts[0].fX;
2837 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2838 SkScalar B = 2 * (pts[1].fX - C);
2839 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2840 }
2841 return xt < x ? dir : 0;
2842}
2843
2844static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2845 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2846 if (y0 == y1) {
2847 return true;
2848 }
2849 if (y0 < y1) {
2850 return y1 <= y2;
2851 } else {
2852 return y1 >= y2;
2853 }
2854}
2855
2856static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2857 SkPoint dst[5];
2858 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2861 n = SkChopQuadAtYExtrema(pts, dst);
2862 pts = dst;
2863 }
2864 int w = winding_mono_quad(pts, x, y);
2865 if (n > 0) {
2866 w += winding_mono_quad(&pts[2], x, y);
2867 }
2868 return w;
2869}
2870
2871static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2872 SkScalar x0 = pts[0].fX;
2873 SkScalar y0 = pts[0].fY;
2874 SkScalar x1 = pts[1].fX;
2875 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002876
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 int dir = 1;
2880 if (y0 > y1) {
2881 SkTSwap(y0, y1);
2882 dir = -1;
2883 }
2884 if (y < y0 || y >= y1) {
2885 return 0;
2886 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2889 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 if (SkScalarSignAsInt(cross) == dir) {
2892 dir = 0;
2893 }
2894 return dir;
2895}
2896
reed@google.com4db592c2013-10-30 17:39:43 +00002897static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2898 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2899}
2900
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901bool SkPath::contains(SkScalar x, SkScalar y) const {
2902 bool isInverse = this->isInverseFillType();
2903 if (this->isEmpty()) {
2904 return isInverse;
2905 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002906
reed@google.com4db592c2013-10-30 17:39:43 +00002907 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002908 return isInverse;
2909 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002910
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002911 SkPath::Iter iter(*this, true);
2912 bool done = false;
2913 int w = 0;
2914 do {
2915 SkPoint pts[4];
2916 switch (iter.next(pts, false)) {
2917 case SkPath::kMove_Verb:
2918 case SkPath::kClose_Verb:
2919 break;
2920 case SkPath::kLine_Verb:
2921 w += winding_line(pts, x, y);
2922 break;
2923 case SkPath::kQuad_Verb:
2924 w += winding_quad(pts, x, y);
2925 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002926 case SkPath::kConic_Verb:
2927 SkASSERT(0);
2928 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 case SkPath::kCubic_Verb:
2930 w += winding_cubic(pts, x, y);
2931 break;
2932 case SkPath::kDone_Verb:
2933 done = true;
2934 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002935 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002937
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002938 switch (this->getFillType()) {
2939 case SkPath::kEvenOdd_FillType:
2940 case SkPath::kInverseEvenOdd_FillType:
2941 w &= 1;
2942 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002943 default:
2944 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 }
2946 return SkToBool(w);
2947}