blob: 4f5fffb7dd18ae966d9ab5c58c03b22e15979d11 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000012#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
40 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
41 }
42
43 ~SkAutoDisableDirectionCheck() {
44 fPath->fDirection = fSaved;
45 }
46
47private:
48 SkPath* fPath;
49 SkPath::Direction fSaved;
50};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000128 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000130 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131#endif
132{
133 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700134 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182}
183
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000184bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185 // note: don't need to look at isConvex or bounds, since just comparing the
186 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000187 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000188 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000189}
190
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191void SkPath::swap(SkPath& that) {
192 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 if (this != &that) {
195 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000196 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
199 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700200 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000201#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000202 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
203#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 }
205}
206
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000207static inline bool check_edge_against_rect(const SkPoint& p0,
208 const SkPoint& p1,
209 const SkRect& rect,
210 SkPath::Direction dir) {
211 const SkPoint* edgeBegin;
212 SkVector v;
213 if (SkPath::kCW_Direction == dir) {
214 v = p1 - p0;
215 edgeBegin = &p0;
216 } else {
217 v = p0 - p1;
218 edgeBegin = &p1;
219 }
220 if (v.fX || v.fY) {
221 // check the cross product of v with the vec from edgeBegin to each rect corner
222 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
223 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
224 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
225 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
226 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
227 return false;
228 }
229 }
230 return true;
231}
232
233bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
234 // This only handles non-degenerate convex paths currently.
235 if (kConvex_Convexity != this->getConvexity()) {
236 return false;
237 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000238
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239 Direction direction;
240 if (!this->cheapComputeDirection(&direction)) {
241 return false;
242 }
243
244 SkPoint firstPt;
245 SkPoint prevPt;
246 RawIter iter(*this);
247 SkPath::Verb verb;
248 SkPoint pts[4];
249 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000250 SkDEBUGCODE(int segmentCount = 0;)
251 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000252
253 while ((verb = iter.next(pts)) != kDone_Verb) {
254 int nextPt = -1;
255 switch (verb) {
256 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 SkDEBUGCODE(++moveCnt);
259 firstPt = prevPt = pts[0];
260 break;
261 case kLine_Verb:
262 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000263 SkASSERT(moveCnt && !closeCount);
264 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 break;
266 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000267 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000268 SkASSERT(moveCnt && !closeCount);
269 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 nextPt = 2;
271 break;
272 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000273 SkASSERT(moveCnt && !closeCount);
274 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 nextPt = 3;
276 break;
277 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 break;
280 default:
281 SkDEBUGFAIL("unknown verb");
282 }
283 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800284 if (SkPath::kConic_Verb == verb) {
285 SkConic orig;
286 orig.set(pts, iter.conicWeight());
287 SkPoint quadPts[5];
288 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
289 SK_ALWAYSBREAK(2 == count);
290
291 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
292 return false;
293 }
294 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
295 return false;
296 }
297 } else {
298 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
299 return false;
300 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 }
302 prevPt = pts[nextPt];
303 }
304 }
305
306 return check_edge_against_rect(prevPt, firstPt, rect, direction);
307}
308
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000309uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000310 uint32_t genID = fPathRef->genID();
311#ifdef SK_BUILD_FOR_ANDROID
312 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
313 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
314#endif
315 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000316}
djsollen@google.come63793a2012-03-21 15:39:03 +0000317
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000318#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000319const SkPath* SkPath::getSourcePath() const {
320 return fSourcePath;
321}
322
323void SkPath::setSourcePath(const SkPath* path) {
324 fSourcePath = path;
325}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000326#endif
327
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328void SkPath::reset() {
329 SkDEBUGCODE(this->validate();)
330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000331 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000332 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333}
334
335void SkPath::rewind() {
336 SkDEBUGCODE(this->validate();)
337
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000338 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000339 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}
341
reed@google.com7e6c4d12012-05-10 14:05:43 +0000342bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000343 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000344
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000345 if (2 == verbCount) {
346 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
347 if (kLine_Verb == fPathRef->atVerb(1)) {
348 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000349 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000350 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000351 line[0] = pts[0];
352 line[1] = pts[1];
353 }
354 return true;
355 }
356 }
357 return false;
358}
359
caryclark@google.comf1316942011-07-26 19:54:45 +0000360/*
361 Determines if path is a rect by keeping track of changes in direction
362 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364 The direction is computed such that:
365 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000366 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000367 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000368 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370A rectangle cycles up/right/down/left or up/left/down/right.
371
372The test fails if:
373 The path is closed, and followed by a line.
374 A second move creates a new endpoint.
375 A diagonal line is parsed.
376 There's more than four changes of direction.
377 There's a discontinuity on the line (e.g., a move in the middle)
378 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The path contains a quadratic or cubic.
380 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000381 *The rectangle doesn't complete a cycle.
382 *The final point isn't equal to the first point.
383
384 *These last two conditions we relax if we have a 3-edge path that would
385 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387It's OK if the path has:
388 Several colinear line segments composing a rectangle side.
389 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000390
caryclark@google.comf1316942011-07-26 19:54:45 +0000391The direction takes advantage of the corners found since opposite sides
392must travel in opposite directions.
393
394FIXME: Allow colinear quads and cubics to be treated like lines.
395FIXME: If the API passes fill-only, return true if the filled stroke
396 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000397
398 first,last,next direction state-machine:
399 0x1 is set if the segment is horizontal
400 0x2 is set if the segment is moving to the right or down
401 thus:
402 two directions are opposites iff (dirA ^ dirB) == 0x2
403 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000406static int rect_make_dir(SkScalar dx, SkScalar dy) {
407 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
408}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000409bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
410 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 int corners = 0;
412 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000413 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000414 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000415 first.set(0, 0);
416 last.set(0, 0);
417 int firstDirection = 0;
418 int lastDirection = 0;
419 int nextDirection = 0;
420 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000421 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000422 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000423 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
424 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000426 savePts = pts;
427 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 autoClose = true;
429 case kLine_Verb: {
430 SkScalar left = last.fX;
431 SkScalar top = last.fY;
432 SkScalar right = pts->fX;
433 SkScalar bottom = pts->fY;
434 ++pts;
435 if (left != right && top != bottom) {
436 return false; // diagonal
437 }
438 if (left == right && top == bottom) {
439 break; // single point on side OK
440 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000441 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 if (0 == corners) {
443 firstDirection = nextDirection;
444 first = last;
445 last = pts[-1];
446 corners = 1;
447 closedOrMoved = false;
448 break;
449 }
450 if (closedOrMoved) {
451 return false; // closed followed by a line
452 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000453 if (autoClose && nextDirection == firstDirection) {
454 break; // colinear with first
455 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 closedOrMoved = autoClose;
457 if (lastDirection != nextDirection) {
458 if (++corners > 4) {
459 return false; // too many direction changes
460 }
461 }
462 last = pts[-1];
463 if (lastDirection == nextDirection) {
464 break; // colinear segment
465 }
466 // Possible values for corners are 2, 3, and 4.
467 // When corners == 3, nextDirection opposes firstDirection.
468 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000469 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
471 if ((directionCycle ^ turn) != nextDirection) {
472 return false; // direction didn't follow cycle
473 }
474 break;
475 }
476 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000477 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 case kCubic_Verb:
479 return false; // quadratic, cubic not allowed
480 case kMove_Verb:
481 last = *pts++;
482 closedOrMoved = true;
483 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000484 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000485 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000486 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000488 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 lastDirection = nextDirection;
490 }
491 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000492 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000493 if (!result) {
494 // check if we are just an incomplete rectangle, in which case we can
495 // return true, but not claim to be closed.
496 // e.g.
497 // 3 sided rectangle
498 // 4 sided but the last edge is not long enough to reach the start
499 //
500 SkScalar closeX = first.x() - last.x();
501 SkScalar closeY = first.y() - last.y();
502 if (closeX && closeY) {
503 return false; // we're diagonal, abort (can we ever reach this?)
504 }
505 int closeDirection = rect_make_dir(closeX, closeY);
506 // make sure the close-segment doesn't double-back on itself
507 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
508 result = true;
509 autoClose = false; // we are not closed
510 }
511 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000512 if (savePts) {
513 *ptsPtr = savePts;
514 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000515 if (result && isClosed) {
516 *isClosed = autoClose;
517 }
518 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000519 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000520 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000521 return result;
522}
523
robertphillips4f662e62014-12-29 14:06:51 -0800524bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000525 SkDEBUGCODE(this->validate();)
526 int currVerb = 0;
527 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800528 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800529 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800530 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000531 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800532 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800533 int32_t num = SkToS32(pts - first);
534 if (num) {
535 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800536 } else {
537 // 'pts' isn't updated for open rects
538 *rect = this->getBounds();
539 }
540 }
541 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000542}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000543
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000545 SkDEBUGCODE(this->validate();)
546 int currVerb = 0;
547 const SkPoint* pts = fPathRef->points();
548 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000549 Direction testDirs[2];
550 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000551 return false;
552 }
553 const SkPoint* last = pts;
554 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000556 testRects[0].set(first, SkToS32(last - first));
557 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 if (testRects[0].contains(testRects[1])) {
559 if (rects) {
560 rects[0] = testRects[0];
561 rects[1] = testRects[1];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[0];
565 dirs[1] = testDirs[1];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 if (testRects[1].contains(testRects[0])) {
570 if (rects) {
571 rects[0] = testRects[1];
572 rects[1] = testRects[0];
573 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000574 if (dirs) {
575 dirs[0] = testDirs[1];
576 dirs[1] = testDirs[0];
577 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000578 return true;
579 }
580 }
581 return false;
582}
583
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584int SkPath::countPoints() const {
585 return fPathRef->countPoints();
586}
587
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000588int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 SkDEBUGCODE(this->validate();)
590
591 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000592 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000593 int count = SkMin32(max, fPathRef->countPoints());
594 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
595 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596}
597
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000598SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000599 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
600 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000601 }
602 return SkPoint::Make(0, 0);
603}
604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000605int SkPath::countVerbs() const {
606 return fPathRef->countVerbs();
607}
608
609static inline void copy_verbs_reverse(uint8_t* inorderDst,
610 const uint8_t* reversedSrc,
611 int count) {
612 for (int i = 0; i < count; ++i) {
613 inorderDst[i] = reversedSrc[~i];
614 }
615}
616
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617int SkPath::getVerbs(uint8_t dst[], int max) const {
618 SkDEBUGCODE(this->validate();)
619
620 SkASSERT(max >= 0);
621 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = SkMin32(max, fPathRef->countVerbs());
623 copy_verbs_reverse(dst, fPathRef->verbs(), count);
624 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625}
626
reed@google.com294dd7b2011-10-11 11:58:32 +0000627bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 SkDEBUGCODE(this->validate();)
629
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000631 if (count > 0) {
632 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000635 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000637 if (lastPt) {
638 lastPt->set(0, 0);
639 }
640 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641}
642
643void SkPath::setLastPt(SkScalar x, SkScalar y) {
644 SkDEBUGCODE(this->validate();)
645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 if (count == 0) {
648 this->moveTo(x, y);
649 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 SkPathRef::Editor ed(&fPathRef);
651 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653}
654
reed@google.com04863fa2011-05-15 04:08:24 +0000655void SkPath::setConvexity(Convexity c) {
656 if (fConvexity != c) {
657 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000658 }
659}
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661//////////////////////////////////////////////////////////////////////////////
662// Construction methods
663
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000664#define DIRTY_AFTER_EDIT \
665 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000666 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000667 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000668 } while (0)
669
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670void SkPath::incReserve(U16CPU inc) {
671 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 SkDEBUGCODE(this->validate();)
674}
675
676void SkPath::moveTo(SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000681 // remember our index
682 fLastMoveToIndex = fPathRef->countPoints();
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700685
686 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687}
688
689void SkPath::rMoveTo(SkScalar x, SkScalar y) {
690 SkPoint pt;
691 this->getLastPt(&pt);
692 this->moveTo(pt.fX + x, pt.fY + y);
693}
694
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000695void SkPath::injectMoveToIfNeeded() {
696 if (fLastMoveToIndex < 0) {
697 SkScalar x, y;
698 if (fPathRef->countVerbs() == 0) {
699 x = y = 0;
700 } else {
701 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
702 x = pt.fX;
703 y = pt.fY;
704 }
705 this->moveTo(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::lineTo(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000712 this->injectMoveToIfNeeded();
713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716
reed@google.comb54455e2011-05-16 14:16:04 +0000717 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718}
719
720void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000721 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 SkPoint pt;
723 this->getLastPt(&pt);
724 this->lineTo(pt.fX + x, pt.fY + y);
725}
726
727void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
728 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000729
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000730 this->injectMoveToIfNeeded();
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
733 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 pts[0].set(x1, y1);
735 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000736
reed@google.comb54455e2011-05-16 14:16:04 +0000737 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738}
739
740void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000741 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 SkPoint pt;
743 this->getLastPt(&pt);
744 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
745}
746
reed@google.com277c3f82013-05-31 15:17:50 +0000747void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
748 SkScalar w) {
749 // check for <= 0 or NaN with this test
750 if (!(w > 0)) {
751 this->lineTo(x2, y2);
752 } else if (!SkScalarIsFinite(w)) {
753 this->lineTo(x1, y1);
754 this->lineTo(x2, y2);
755 } else if (SK_Scalar1 == w) {
756 this->quadTo(x1, y1, x2, y2);
757 } else {
758 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000759
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000760 this->injectMoveToIfNeeded();
761
reed@google.com277c3f82013-05-31 15:17:50 +0000762 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000763 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000766
reed@google.com277c3f82013-05-31 15:17:50 +0000767 DIRTY_AFTER_EDIT;
768 }
769}
770
771void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
772 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
777}
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 pts[0].set(x1, y1);
788 pts[1].set(x2, y2);
789 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790
reed@google.comb54455e2011-05-16 14:16:04 +0000791 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792}
793
794void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
795 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000796 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkPoint pt;
798 this->getLastPt(&pt);
799 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
800 pt.fX + x3, pt.fY + y3);
801}
802
803void SkPath::close() {
804 SkDEBUGCODE(this->validate();)
805
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000806 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 case kLine_Verb:
810 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000811 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 case kMove_Verb: {
814 SkPathRef::Editor ed(&fPathRef);
815 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 }
reed@google.com277c3f82013-05-31 15:17:50 +0000818 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000819 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000820 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000821 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000822 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000823 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 }
825 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826
827 // signal that we need a moveTo to follow us (unless we're done)
828#if 0
829 if (fLastMoveToIndex >= 0) {
830 fLastMoveToIndex = ~fLastMoveToIndex;
831 }
832#else
833 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
834#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835}
836
837///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000838
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000839static void assert_known_direction(int dir) {
840 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
841}
842
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843void SkPath::addRect(const SkRect& rect, Direction dir) {
844 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
845}
846
847void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
848 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000849 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000850 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
851 SkAutoDisableDirectionCheck addc(this);
852
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
854
855 this->incReserve(5);
856
857 this->moveTo(left, top);
858 if (dir == kCCW_Direction) {
859 this->lineTo(left, bottom);
860 this->lineTo(right, bottom);
861 this->lineTo(right, top);
862 } else {
863 this->lineTo(right, top);
864 this->lineTo(right, bottom);
865 this->lineTo(left, bottom);
866 }
867 this->close();
868}
869
reed@google.com744faba2012-05-29 19:54:52 +0000870void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
871 SkDEBUGCODE(this->validate();)
872 if (count <= 0) {
873 return;
874 }
875
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000876 fLastMoveToIndex = fPathRef->countPoints();
877
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000878 // +close makes room for the extra kClose_Verb
879 SkPathRef::Editor ed(&fPathRef, count+close, count);
880
881 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000882 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000883 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
884 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000885 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000886
reed@google.com744faba2012-05-29 19:54:52 +0000887 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000888 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000889 }
890
reed@google.com744faba2012-05-29 19:54:52 +0000891 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000892 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000893}
894
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000895#include "SkGeometry.h"
896
reedf90ea012015-01-29 12:03:58 -0800897static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
898 SkPoint* pt) {
899 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000900 // Chrome uses this path to move into and out of ovals. If not
901 // treated as a special case the moves can distort the oval's
902 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800903 pt->set(oval.fRight, oval.centerY());
904 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000905 } else if (0 == oval.width() && 0 == oval.height()) {
906 // Chrome will sometimes create 0 radius round rects. Having degenerate
907 // quad segments in the path prevents the path from being recognized as
908 // a rect.
909 // TODO: optimizing the case where only one of width or height is zero
910 // should also be considered. This case, however, doesn't seem to be
911 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800912 pt->set(oval.fRight, oval.fTop);
913 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000914 }
reedf90ea012015-01-29 12:03:58 -0800915 return false;
916}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000917
reedd5d27d92015-02-09 13:54:43 -0800918// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
919//
920static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
921 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
922 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
923 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000924
925 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800926 loss in radians-conversion and/or sin/cos, we may end up with coincident
927 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
928 of drawing a nearly complete circle (good).
929 e.g. canvas.drawArc(0, 359.99, ...)
930 -vs- canvas.drawArc(0, 359.9, ...)
931 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000932 */
reedd5d27d92015-02-09 13:54:43 -0800933 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000934 SkScalar sw = SkScalarAbs(sweepAngle);
935 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
936 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
937 // make a guess at a tiny angle (in radians) to tweak by
938 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
939 // not sure how much will be enough, so we use a loop
940 do {
941 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800942 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
943 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000944 }
945 }
reedd5d27d92015-02-09 13:54:43 -0800946 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
947}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000948
reedd5d27d92015-02-09 13:54:43 -0800949#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
950static int build_arc_points(const SkRect& oval, const SkVector& start, const SkVector& stop,
951 SkRotationDirection dir, SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000952 SkMatrix matrix;
953
954 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
955 matrix.postTranslate(oval.centerX(), oval.centerY());
956
reedd5d27d92015-02-09 13:54:43 -0800957 return SkBuildQuadArc(start, stop, dir, &matrix, pts);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000958}
reedd5d27d92015-02-09 13:54:43 -0800959#else
960static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
961 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc]) {
962 SkMatrix matrix;
963
964 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
965 matrix.postTranslate(oval.centerX(), oval.centerY());
966
967 return SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
968}
969#endif
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000970
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000971void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000973 SkRRect rrect;
974 rrect.setRectRadii(rect, (const SkVector*) radii);
975 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976}
977
reed1b28a3a2014-12-17 14:42:09 -0800978#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000979/* The inline clockwise and counterclockwise round rect quad approximations
980 make it easier to see the symmetry patterns used by add corner quads.
981Clockwise corner value
982 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
983 path->quadTo(rect.fLeft, rect.fTop + offPtY,
984 rect.fLeft + midPtX, rect.fTop + midPtY);
985 path->quadTo(rect.fLeft + offPtX, rect.fTop,
986 rect.fLeft + rx, rect.fTop);
987
988 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
989 path->quadTo(rect.fRight - offPtX, rect.fTop,
990 rect.fRight - midPtX, rect.fTop + midPtY);
991 path->quadTo(rect.fRight, rect.fTop + offPtY,
992 rect.fRight, rect.fTop + ry);
993
994 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
995 path->quadTo(rect.fRight, rect.fBottom - offPtY,
996 rect.fRight - midPtX, rect.fBottom - midPtY);
997 path->quadTo(rect.fRight - offPtX, rect.fBottom,
998 rect.fRight - rx, rect.fBottom);
999
1000 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
1001 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
1002 rect.fLeft + midPtX, rect.fBottom - midPtY);
1003 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
1004 rect.fLeft, rect.fBottom - ry);
1005
1006Counterclockwise
1007 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
1008 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
1009 rect.fLeft + midPtX, rect.fBottom - midPtY);
1010 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
1011 rect.fLeft + rx, rect.fBottom);
1012
1013 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
1014 path->quadTo(rect.fRight - offPtX, rect.fBottom,
1015 rect.fRight - midPtX, rect.fBottom - midPtY);
1016 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1017 rect.fRight, rect.fBottom - ry);
1018
1019 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1020 path->quadTo(rect.fRight, rect.fTop + offPtY,
1021 rect.fRight - midPtX, rect.fTop + midPtY);
1022 path->quadTo(rect.fRight - offPtX, rect.fTop,
1023 rect.fRight - rx, rect.fTop);
1024
1025 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1026 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1027 rect.fLeft + midPtX, rect.fTop + midPtY);
1028 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1029 rect.fLeft, rect.fTop + ry);
1030*/
1031static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1032 SkRRect::Corner corner, SkPath::Direction dir) {
1033 const SkRect& rect = rrect.rect();
1034 const SkVector& radii = rrect.radii(corner);
1035 SkScalar rx = radii.fX;
1036 SkScalar ry = radii.fY;
1037 // The mid point of the quadratic arc approximation is half way between the two
1038 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001039 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1040 SkScalar midPtX = rx * mid;
1041 SkScalar midPtY = ry * mid;
1042 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1043 SkScalar offPtX = rx * control;
1044 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001045 static const int kCornerPts = 5;
1046 SkScalar xOff[kCornerPts];
1047 SkScalar yOff[kCornerPts];
1048
1049 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1050 SkASSERT(dir == SkPath::kCCW_Direction
1051 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1052 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1053 xOff[0] = xOff[1] = 0;
1054 xOff[2] = midPtX;
1055 xOff[3] = offPtX;
1056 xOff[4] = rx;
1057 yOff[0] = ry;
1058 yOff[1] = offPtY;
1059 yOff[2] = midPtY;
1060 yOff[3] = yOff[4] = 0;
1061 } else {
1062 xOff[0] = rx;
1063 xOff[1] = offPtX;
1064 xOff[2] = midPtX;
1065 xOff[3] = xOff[4] = 0;
1066 yOff[0] = yOff[1] = 0;
1067 yOff[2] = midPtY;
1068 yOff[3] = offPtY;
1069 yOff[4] = ry;
1070 }
1071 if ((corner - 1) & 2) {
1072 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1073 for (int i = 0; i < kCornerPts; ++i) {
1074 xOff[i] = rect.fLeft + xOff[i];
1075 }
1076 } else {
1077 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1078 for (int i = 0; i < kCornerPts; ++i) {
1079 xOff[i] = rect.fRight - xOff[i];
1080 }
1081 }
1082 if (corner < SkRRect::kLowerRight_Corner) {
1083 for (int i = 0; i < kCornerPts; ++i) {
1084 yOff[i] = rect.fTop + yOff[i];
1085 }
1086 } else {
1087 for (int i = 0; i < kCornerPts; ++i) {
1088 yOff[i] = rect.fBottom - yOff[i];
1089 }
1090 }
1091
1092 SkPoint lastPt;
1093 SkAssertResult(path->getLastPt(&lastPt));
1094 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1095 path->lineTo(xOff[0], yOff[0]);
1096 }
1097 if (rx || ry) {
1098 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1099 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1100 } else {
1101 path->lineTo(xOff[2], yOff[2]);
1102 path->lineTo(xOff[4], yOff[4]);
1103 }
1104}
reed1b28a3a2014-12-17 14:42:09 -08001105#endif
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001106
reed@google.com4ed0fb72012-12-12 20:48:18 +00001107void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108 assert_known_direction(dir);
1109
1110 if (rrect.isEmpty()) {
1111 return;
1112 }
1113
reed@google.com4ed0fb72012-12-12 20:48:18 +00001114 const SkRect& bounds = rrect.getBounds();
1115
1116 if (rrect.isRect()) {
1117 this->addRect(bounds, dir);
1118 } else if (rrect.isOval()) {
1119 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001120 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001121 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001123 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001124 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001125
reed1b28a3a2014-12-17 14:42:09 -08001126#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001127 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001128 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001129 this->moveTo(bounds.fLeft,
1130 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1131 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1132 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1133 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1134 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001135 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001136 this->moveTo(bounds.fLeft,
1137 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1138 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1139 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1140 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1141 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001142 }
reed1b28a3a2014-12-17 14:42:09 -08001143#else
1144 const SkScalar L = bounds.fLeft;
1145 const SkScalar T = bounds.fTop;
1146 const SkScalar R = bounds.fRight;
1147 const SkScalar B = bounds.fBottom;
1148 const SkScalar W = SK_ScalarRoot2Over2;
1149
1150 this->incReserve(13);
1151 if (kCW_Direction == dir) {
1152 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1153
1154 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1155 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1156
1157 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1158 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1159
1160 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1161 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1162
1163 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1164 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1165 } else {
1166 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1167
1168 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1169 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1170
1171 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1172 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1173
1174 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1175 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1176
1177 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1178 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1179 }
1180#endif
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001181 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001182 }
reed5bcbe912014-12-15 12:28:33 -08001183 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001184}
1185
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001186bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001187 int count = fPathRef->countVerbs();
1188 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1189 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190 if (*verbs == kLine_Verb ||
1191 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001192 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193 *verbs == kCubic_Verb) {
1194 return false;
1195 }
1196 ++verbs;
1197 }
1198 return true;
1199}
1200
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001201void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1202 Direction dir) {
1203 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001204
humper@google.com75e3ca12013-04-08 21:44:11 +00001205 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001206 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001207 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001208 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001209 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1210 return;
1211 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001212
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001213 SkRRect rrect;
1214 rrect.setRectXY(rect, rx, ry);
1215 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001216}
1217
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001219 assert_known_direction(dir);
1220
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001221 /* If addOval() is called after previous moveTo(),
1222 this path is still marked as an oval. This is used to
1223 fit into WebKit's calling sequences.
1224 We can't simply check isEmpty() in this case, as additional
1225 moveTo() would mark the path non empty.
1226 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001227 bool isOval = hasOnlyMoveTos();
1228 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001229 fDirection = dir;
1230 } else {
1231 fDirection = kUnknown_Direction;
1232 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001233
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001234 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 SkAutoPathBoundsUpdate apbu(this, oval);
1237
reed220f9262014-12-17 08:21:04 -08001238#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239 SkScalar cx = oval.centerX();
1240 SkScalar cy = oval.centerY();
1241 SkScalar rx = SkScalarHalf(oval.width());
1242 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1245 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1246 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1247 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1248
1249 /*
reed220f9262014-12-17 08:21:04 -08001250 To handle imprecision in computing the center and radii, we revert to
1251 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1252 to ensure that we don't exceed the oval's bounds *ever*, since we want
1253 to use oval for our fast-bounds, rather than have to recompute it.
1254 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 const SkScalar L = oval.fLeft; // cx - rx
1256 const SkScalar T = oval.fTop; // cy - ry
1257 const SkScalar R = oval.fRight; // cx + rx
1258 const SkScalar B = oval.fBottom; // cy + ry
1259
1260 this->incReserve(17); // 8 quads + close
1261 this->moveTo(R, cy);
1262 if (dir == kCCW_Direction) {
1263 this->quadTo( R, cy - sy, cx + mx, cy - my);
1264 this->quadTo(cx + sx, T, cx , T);
1265 this->quadTo(cx - sx, T, cx - mx, cy - my);
1266 this->quadTo( L, cy - sy, L, cy );
1267 this->quadTo( L, cy + sy, cx - mx, cy + my);
1268 this->quadTo(cx - sx, B, cx , B);
1269 this->quadTo(cx + sx, B, cx + mx, cy + my);
1270 this->quadTo( R, cy + sy, R, cy );
1271 } else {
1272 this->quadTo( R, cy + sy, cx + mx, cy + my);
1273 this->quadTo(cx + sx, B, cx , B);
1274 this->quadTo(cx - sx, B, cx - mx, cy + my);
1275 this->quadTo( L, cy + sy, L, cy );
1276 this->quadTo( L, cy - sy, cx - mx, cy - my);
1277 this->quadTo(cx - sx, T, cx , T);
1278 this->quadTo(cx + sx, T, cx + mx, cy - my);
1279 this->quadTo( R, cy - sy, R, cy );
1280 }
reed220f9262014-12-17 08:21:04 -08001281#else
1282 const SkScalar L = oval.fLeft;
1283 const SkScalar T = oval.fTop;
1284 const SkScalar R = oval.fRight;
1285 const SkScalar B = oval.fBottom;
1286 const SkScalar cx = oval.centerX();
1287 const SkScalar cy = oval.centerY();
1288 const SkScalar weight = SK_ScalarRoot2Over2;
1289
1290 this->incReserve(9); // move + 4 conics
1291 this->moveTo(R, cy);
1292 if (dir == kCCW_Direction) {
1293 this->conicTo(R, T, cx, T, weight);
1294 this->conicTo(L, T, L, cy, weight);
1295 this->conicTo(L, B, cx, B, weight);
1296 this->conicTo(R, B, R, cy, weight);
1297 } else {
1298 this->conicTo(R, B, cx, B, weight);
1299 this->conicTo(L, B, L, cy, weight);
1300 this->conicTo(L, T, cx, T, weight);
1301 this->conicTo(R, T, R, cy, weight);
1302 }
1303#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305
robertphillips@google.com466310d2013-12-03 16:43:54 +00001306 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001307
robertphillips@google.com466310d2013-12-03 16:43:54 +00001308 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001309}
1310
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1312 if (r > 0) {
1313 SkRect rect;
1314 rect.set(x - r, y - r, x + r, y + r);
1315 this->addOval(rect, dir);
1316 }
1317}
1318
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1320 bool forceMoveTo) {
1321 if (oval.width() < 0 || oval.height() < 0) {
1322 return;
1323 }
1324
reedf90ea012015-01-29 12:03:58 -08001325 if (fPathRef->countVerbs() == 0) {
1326 forceMoveTo = true;
1327 }
1328
1329 SkPoint lonePt;
1330 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1331 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1332 return;
1333 }
1334
reedd5d27d92015-02-09 13:54:43 -08001335 SkVector startV, stopV;
1336 SkRotationDirection dir;
1337 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1338
1339#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 SkPoint pts[kSkBuildQuadArcStorage];
reedd5d27d92015-02-09 13:54:43 -08001341 int count = build_arc_points(oval, startV, stopV, dir, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkASSERT((count & 1) == 1);
1343
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 this->incReserve(count);
1345 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1346 for (int i = 1; i < count; i += 2) {
1347 this->quadTo(pts[i], pts[i+1]);
1348 }
reedd5d27d92015-02-09 13:54:43 -08001349#else
1350 SkConic conics[SkConic::kMaxConicsForArc];
1351 int count = build_arc_conics(oval, startV, stopV, dir, conics);
1352 if (count) {
1353 this->incReserve(count * 2 + 1);
1354 const SkPoint& pt = conics[0].fPts[0];
1355 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1356 for (int i = 0; i < count; ++i) {
1357 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1358 }
1359 }
1360#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361}
1362
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001363void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 if (oval.isEmpty() || 0 == sweepAngle) {
1365 return;
1366 }
1367
1368 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1369
1370 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1371 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001372 } else {
1373 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375}
1376
1377/*
1378 Need to handle the case when the angle is sharp, and our computed end-points
1379 for the arc go behind pt1 and/or p2...
1380*/
reedc7789042015-01-29 12:59:11 -08001381void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001382 if (radius == 0) {
1383 this->lineTo(x1, y1);
1384 return;
1385 }
1386
1387 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001388
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 // need to know our prev pt so we can construct tangent vectors
1390 {
1391 SkPoint start;
1392 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001393 // Handle degenerate cases by adding a line to the first point and
1394 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 before.setNormalize(x1 - start.fX, y1 - start.fY);
1396 after.setNormalize(x2 - x1, y2 - y1);
1397 }
reed@google.comabf15c12011-01-18 20:35:51 +00001398
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 SkScalar cosh = SkPoint::DotProduct(before, after);
1400 SkScalar sinh = SkPoint::CrossProduct(before, after);
1401
1402 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001403 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 return;
1405 }
reed@google.comabf15c12011-01-18 20:35:51 +00001406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1408 if (dist < 0) {
1409 dist = -dist;
1410 }
1411
1412 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1413 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1414 SkRotationDirection arcDir;
1415
1416 // now turn before/after into normals
1417 if (sinh > 0) {
1418 before.rotateCCW();
1419 after.rotateCCW();
1420 arcDir = kCW_SkRotationDirection;
1421 } else {
1422 before.rotateCW();
1423 after.rotateCW();
1424 arcDir = kCCW_SkRotationDirection;
1425 }
1426
1427 SkMatrix matrix;
1428 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 matrix.setScale(radius, radius);
1431 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1432 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001433
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001435
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 this->incReserve(count);
1437 // [xx,yy] == pts[0]
1438 this->lineTo(xx, yy);
1439 for (int i = 1; i < count; i += 2) {
1440 this->quadTo(pts[i], pts[i+1]);
1441 }
1442}
1443
1444///////////////////////////////////////////////////////////////////////////////
1445
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001446void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 SkMatrix matrix;
1448
1449 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001450 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451}
1452
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001453void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001456 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkPoint pts[4];
1458 Verb verb;
1459
1460 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001461 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 while ((verb = iter.next(pts)) != kDone_Verb) {
1463 switch (verb) {
1464 case kMove_Verb:
1465 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001466 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1467 injectMoveToIfNeeded(); // In case last contour is closed
1468 this->lineTo(pts[0]);
1469 } else {
1470 this->moveTo(pts[0]);
1471 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472 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 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001495 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 }
1497}
1498
1499///////////////////////////////////////////////////////////////////////////////
1500
reed@google.com277c3f82013-05-31 15:17:50 +00001501static int pts_in_verb(unsigned verb) {
1502 static const uint8_t gPtsInVerb[] = {
1503 1, // kMove
1504 1, // kLine
1505 2, // kQuad
1506 2, // kConic
1507 3, // kCubic
1508 0, // kClose
1509 0 // kDone
1510 };
1511
1512 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1513 return gPtsInVerb[verb];
1514}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516// ignore the last point of the 1st contour
1517void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 int i, vcount = path.fPathRef->countVerbs();
1519 // exit early if the path is empty, or just has a moveTo.
1520 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 return;
1522 }
1523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 const uint8_t* verbs = path.fPathRef->verbs();
1527 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001528 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001530 SkASSERT(verbs[~0] == kMove_Verb);
1531 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001532 unsigned v = verbs[~i];
1533 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 if (n == 0) {
1535 break;
1536 }
1537 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001538 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 }
1540
1541 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 case kLine_Verb:
1544 this->lineTo(pts[-1].fX, pts[-1].fY);
1545 break;
1546 case kQuad_Verb:
1547 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1548 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001549 case kConic_Verb:
1550 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1551 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 case kCubic_Verb:
1553 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1554 pts[-3].fX, pts[-3].fY);
1555 break;
1556 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001557 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 break;
1559 }
reed@google.com277c3f82013-05-31 15:17:50 +00001560 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001561 }
1562}
1563
reed@google.com63d73742012-01-10 15:33:12 +00001564void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001566
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001567 const SkPoint* pts = src.fPathRef->pointsEnd();
1568 // we will iterator through src's verbs backwards
1569 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1570 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001571 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001572
1573 bool needMove = true;
1574 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001575 while (verbs < verbsEnd) {
1576 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001577 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001578
1579 if (needMove) {
1580 --pts;
1581 this->moveTo(pts->fX, pts->fY);
1582 needMove = false;
1583 }
1584 pts -= n;
1585 switch (v) {
1586 case kMove_Verb:
1587 if (needClose) {
1588 this->close();
1589 needClose = false;
1590 }
1591 needMove = true;
1592 pts += 1; // so we see the point in "if (needMove)" above
1593 break;
1594 case kLine_Verb:
1595 this->lineTo(pts[0]);
1596 break;
1597 case kQuad_Verb:
1598 this->quadTo(pts[1], pts[0]);
1599 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001600 case kConic_Verb:
1601 this->conicTo(pts[1], pts[0], *--conicWeights);
1602 break;
reed@google.com63d73742012-01-10 15:33:12 +00001603 case kCubic_Verb:
1604 this->cubicTo(pts[2], pts[1], pts[0]);
1605 break;
1606 case kClose_Verb:
1607 needClose = true;
1608 break;
1609 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001610 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001611 }
1612 }
1613}
1614
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615///////////////////////////////////////////////////////////////////////////////
1616
1617void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1618 SkMatrix matrix;
1619
1620 matrix.setTranslate(dx, dy);
1621 this->transform(matrix, dst);
1622}
1623
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1625 int level = 2) {
1626 if (--level >= 0) {
1627 SkPoint tmp[7];
1628
1629 SkChopCubicAtHalf(pts, tmp);
1630 subdivide_cubic_to(path, &tmp[0], level);
1631 subdivide_cubic_to(path, &tmp[3], level);
1632 } else {
1633 path->cubicTo(pts[1], pts[2], pts[3]);
1634 }
1635}
1636
1637void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1638 SkDEBUGCODE(this->validate();)
1639 if (dst == NULL) {
1640 dst = (SkPath*)this;
1641 }
1642
tomhudson@google.com8d430182011-06-06 19:11:19 +00001643 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 SkPath tmp;
1645 tmp.fFillType = fFillType;
1646
1647 SkPath::Iter iter(*this, false);
1648 SkPoint pts[4];
1649 SkPath::Verb verb;
1650
reed@google.com4a3b7142012-05-16 17:16:46 +00001651 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 switch (verb) {
1653 case kMove_Verb:
1654 tmp.moveTo(pts[0]);
1655 break;
1656 case kLine_Verb:
1657 tmp.lineTo(pts[1]);
1658 break;
1659 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001660 // promote the quad to a conic
1661 tmp.conicTo(pts[1], pts[2],
1662 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001664 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001665 tmp.conicTo(pts[1], pts[2],
1666 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001667 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 case kCubic_Verb:
1669 subdivide_cubic_to(&tmp, pts);
1670 break;
1671 case kClose_Verb:
1672 tmp.close();
1673 break;
1674 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001675 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 break;
1677 }
1678 }
1679
1680 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001681 SkPathRef::Editor ed(&dst->fPathRef);
1682 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001683 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001685 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1686
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001689 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001690 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001692
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001693 if (kUnknown_Direction == fDirection) {
1694 dst->fDirection = kUnknown_Direction;
1695 } else {
1696 SkScalar det2x2 =
1697 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1698 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1699 if (det2x2 < 0) {
1700 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1701 } else if (det2x2 > 0) {
1702 dst->fDirection = fDirection;
1703 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001704 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001705 dst->fDirection = kUnknown_Direction;
1706 }
1707 }
1708
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709 SkDEBUGCODE(dst->validate();)
1710 }
1711}
1712
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713///////////////////////////////////////////////////////////////////////////////
1714///////////////////////////////////////////////////////////////////////////////
1715
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001716enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001717 kEmptyContour_SegmentState, // The current contour is empty. We may be
1718 // starting processing or we may have just
1719 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001720 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1721 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1722 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723};
1724
1725SkPath::Iter::Iter() {
1726#ifdef SK_DEBUG
1727 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001728 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001731 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732#endif
1733 // need to init enough to make next() harmlessly return kDone_Verb
1734 fVerbs = NULL;
1735 fVerbStop = NULL;
1736 fNeedClose = false;
1737}
1738
1739SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1740 this->setPath(path, forceClose);
1741}
1742
1743void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001744 fPts = path.fPathRef->points();
1745 fVerbs = path.fPathRef->verbs();
1746 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001747 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001749 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 fForceClose = SkToU8(forceClose);
1751 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001752 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753}
1754
1755bool SkPath::Iter::isClosedContour() const {
1756 if (fVerbs == NULL || fVerbs == fVerbStop) {
1757 return false;
1758 }
1759 if (fForceClose) {
1760 return true;
1761 }
1762
1763 const uint8_t* verbs = fVerbs;
1764 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001765
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001766 if (kMove_Verb == *(verbs - 1)) {
1767 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 }
1769
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001770 while (verbs > stop) {
1771 // verbs points one beyond the current verb, decrement first.
1772 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 if (kMove_Verb == v) {
1774 break;
1775 }
1776 if (kClose_Verb == v) {
1777 return true;
1778 }
1779 }
1780 return false;
1781}
1782
1783SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001784 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001786 // A special case: if both points are NaN, SkPoint::operation== returns
1787 // false, but the iterator expects that they are treated as the same.
1788 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001789 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1790 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001791 return kClose_Verb;
1792 }
1793
reed@google.com9e25dbf2012-05-15 17:05:38 +00001794 pts[0] = fLastPt;
1795 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 fLastPt = fMoveTo;
1797 fCloseLine = true;
1798 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001799 } else {
1800 pts[0] = fMoveTo;
1801 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803}
1804
reed@google.com9e25dbf2012-05-15 17:05:38 +00001805const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 if (fSegmentState == kAfterMove_SegmentState) {
1807 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001809 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1812 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001813 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815}
1816
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817void SkPath::Iter::consumeDegenerateSegments() {
1818 // We need to step over anything that will not move the current draw point
1819 // forward before the next move is seen
1820 const uint8_t* lastMoveVerb = 0;
1821 const SkPoint* lastMovePt = 0;
1822 SkPoint lastPt = fLastPt;
1823 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001824 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 switch (verb) {
1826 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 // Keep a record of this most recent move
1828 lastMoveVerb = fVerbs;
1829 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001830 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001831 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001832 fPts++;
1833 break;
1834
1835 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001836 // A close when we are in a segment is always valid except when it
1837 // follows a move which follows a segment.
1838 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 return;
1840 }
1841 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 break;
1844
1845 case kLine_Verb:
1846 if (!IsLineDegenerate(lastPt, fPts[0])) {
1847 if (lastMoveVerb) {
1848 fVerbs = lastMoveVerb;
1849 fPts = lastMovePt;
1850 return;
1851 }
1852 return;
1853 }
1854 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 fPts++;
1857 break;
1858
reed@google.com277c3f82013-05-31 15:17:50 +00001859 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 case kQuad_Verb:
1861 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1862 if (lastMoveVerb) {
1863 fVerbs = lastMoveVerb;
1864 fPts = lastMovePt;
1865 return;
1866 }
1867 return;
1868 }
1869 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001872 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 break;
1874
1875 case kCubic_Verb:
1876 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1877 if (lastMoveVerb) {
1878 fVerbs = lastMoveVerb;
1879 fPts = lastMovePt;
1880 return;
1881 }
1882 return;
1883 }
1884 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 fPts += 3;
1887 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001888
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001890 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 }
1892 }
1893}
1894
reed@google.com4a3b7142012-05-16 17:16:46 +00001895SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001896 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001899 // Close the curve if requested and if there is some curve to close
1900 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 return kLine_Verb;
1903 }
1904 fNeedClose = false;
1905 return kClose_Verb;
1906 }
1907 return kDone_Verb;
1908 }
1909
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001910 // fVerbs is one beyond the current verb, decrement first
1911 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001912 const SkPoint* SK_RESTRICT srcPts = fPts;
1913 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914
1915 switch (verb) {
1916 case kMove_Verb:
1917 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 verb = this->autoClose(pts);
1920 if (verb == kClose_Verb) {
1921 fNeedClose = false;
1922 }
1923 return (Verb)verb;
1924 }
1925 if (fVerbs == fVerbStop) { // might be a trailing moveto
1926 return kDone_Verb;
1927 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001928 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001929 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001931 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 fNeedClose = fForceClose;
1934 break;
1935 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001936 pts[0] = this->cons_moveTo();
1937 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 fLastPt = srcPts[0];
1939 fCloseLine = false;
1940 srcPts += 1;
1941 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001942 case kConic_Verb:
1943 fConicWeights += 1;
1944 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001946 pts[0] = this->cons_moveTo();
1947 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 fLastPt = srcPts[1];
1949 srcPts += 2;
1950 break;
1951 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001952 pts[0] = this->cons_moveTo();
1953 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 fLastPt = srcPts[2];
1955 srcPts += 3;
1956 break;
1957 case kClose_Verb:
1958 verb = this->autoClose(pts);
1959 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001960 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 } else {
1962 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001963 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001965 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 break;
1967 }
1968 fPts = srcPts;
1969 return (Verb)verb;
1970}
1971
1972///////////////////////////////////////////////////////////////////////////////
1973
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001974SkPath::RawIter::RawIter() {
1975#ifdef SK_DEBUG
1976 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001977 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001978 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1979#endif
1980 // need to init enough to make next() harmlessly return kDone_Verb
1981 fVerbs = NULL;
1982 fVerbStop = NULL;
1983}
1984
1985SkPath::RawIter::RawIter(const SkPath& path) {
1986 this->setPath(path);
1987}
1988
1989void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001990 fPts = path.fPathRef->points();
1991 fVerbs = path.fPathRef->verbs();
1992 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001993 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001994 fMoveTo.fX = fMoveTo.fY = 0;
1995 fLastPt.fX = fLastPt.fY = 0;
1996}
1997
1998SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001999 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002000 if (fVerbs == fVerbStop) {
2001 return kDone_Verb;
2002 }
2003
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 // fVerbs points one beyond next verb so decrement first.
2005 unsigned verb = *(--fVerbs);
2006 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002007
2008 switch (verb) {
2009 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002010 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002011 fMoveTo = srcPts[0];
2012 fLastPt = fMoveTo;
2013 srcPts += 1;
2014 break;
2015 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002016 pts[0] = fLastPt;
2017 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002018 fLastPt = srcPts[0];
2019 srcPts += 1;
2020 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002021 case kConic_Verb:
2022 fConicWeights += 1;
2023 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002024 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002025 pts[0] = fLastPt;
2026 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002027 fLastPt = srcPts[1];
2028 srcPts += 2;
2029 break;
2030 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002031 pts[0] = fLastPt;
2032 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002033 fLastPt = srcPts[2];
2034 srcPts += 3;
2035 break;
2036 case kClose_Verb:
2037 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002038 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002039 break;
2040 }
2041 fPts = srcPts;
2042 return (Verb)verb;
2043}
2044
2045///////////////////////////////////////////////////////////////////////////////
2046
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002048 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049*/
2050
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002051size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 SkDEBUGCODE(this->validate();)
2053
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002054 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002055 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002056 return SkAlign4(byteCount);
2057 }
2058
2059 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002060
robertphillips@google.com466310d2013-12-03 16:43:54 +00002061 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002062 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002063 (fDirection << kDirection_SerializationShift) |
2064 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002065
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002066 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002067
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002068 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002069
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002070 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002071 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072}
2073
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002074size_t SkPath::readFromMemory(const void* storage, size_t length) {
2075 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002076
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002077 int32_t packed;
2078 if (!buffer.readS32(&packed)) {
2079 return 0;
2080 }
2081
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2083 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002084 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002085 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002086 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002087
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002088 size_t sizeRead = 0;
2089 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002090 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002091 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002092 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002093 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002094 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002095 // If the buffer is not valid, pathRef should be NULL
2096 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002097 }
2098 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099}
2100
2101///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102
reede05fed02014-12-15 07:59:53 -08002103#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002104#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002105
reed@google.com51bbe752013-01-17 22:07:50 +00002106static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002107 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002108 str->append(label);
2109 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002110
reed@google.com51bbe752013-01-17 22:07:50 +00002111 const SkScalar* values = &pts[0].fX;
2112 count *= 2;
2113
2114 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002115 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002116 if (i < count - 1) {
2117 str->append(", ");
2118 }
2119 }
reed@google.com277c3f82013-05-31 15:17:50 +00002120 if (conicWeight >= 0) {
2121 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002122 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002123 }
caryclark08fa28c2014-10-23 13:08:56 -07002124 str->append(");");
reede05fed02014-12-15 07:59:53 -08002125 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002126 str->append(" // ");
2127 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002128 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002129 if (i < count - 1) {
2130 str->append(", ");
2131 }
2132 }
2133 if (conicWeight >= 0) {
2134 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002135 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002136 }
2137 }
2138 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002139}
2140
caryclarke9562592014-09-15 09:26:09 -07002141void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002142 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 Iter iter(*this, forceClose);
2144 SkPoint pts[4];
2145 Verb verb;
2146
caryclark66a5d8b2014-06-24 08:30:15 -07002147 if (!wStream) {
2148 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2149 }
reed@google.com51bbe752013-01-17 22:07:50 +00002150 SkString builder;
2151
reed@google.com4a3b7142012-05-16 17:16:46 +00002152 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 switch (verb) {
2154 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002155 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
2157 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002158 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 break;
2160 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002161 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002163 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002164 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002165 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002167 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 break;
2169 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002170 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 break;
2172 default:
2173 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2174 verb = kDone_Verb; // stop the loop
2175 break;
2176 }
2177 }
caryclark66a5d8b2014-06-24 08:30:15 -07002178 if (wStream) {
2179 wStream->writeText(builder.c_str());
2180 } else {
2181 SkDebugf("%s", builder.c_str());
2182 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183}
2184
reed@android.come522ca52009-11-23 20:10:41 +00002185void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002186 this->dump(NULL, false, false);
2187}
2188
2189void SkPath::dumpHex() const {
2190 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002191}
2192
2193#ifdef SK_DEBUG
2194void SkPath::validate() const {
2195 SkASSERT(this != NULL);
2196 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002197
djsollen@google.com077348c2012-10-22 20:23:32 +00002198#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002199 if (!fBoundsIsDirty) {
2200 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002201
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002202 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002203 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002204
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002205 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002206 // if we're empty, fBounds may be empty but translated, so we can't
2207 // necessarily compare to bounds directly
2208 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2209 // be [2, 2, 2, 2]
2210 SkASSERT(bounds.isEmpty());
2211 SkASSERT(fBounds.isEmpty());
2212 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002213 if (bounds.isEmpty()) {
2214 SkASSERT(fBounds.isEmpty());
2215 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 if (!fBounds.isEmpty()) {
2217 SkASSERT(fBounds.contains(bounds));
2218 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002219 }
reed@android.come522ca52009-11-23 20:10:41 +00002220 }
2221 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002222#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002223}
djsollen@google.com077348c2012-10-22 20:23:32 +00002224#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002225
reed@google.com04863fa2011-05-15 04:08:24 +00002226///////////////////////////////////////////////////////////////////////////////
2227
reed@google.com0b7b9822011-05-16 12:29:27 +00002228static int sign(SkScalar x) { return x < 0; }
2229#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002230
robertphillipsc506e302014-09-16 09:43:31 -07002231enum DirChange {
2232 kLeft_DirChange,
2233 kRight_DirChange,
2234 kStraight_DirChange,
2235 kBackwards_DirChange,
2236
2237 kInvalid_DirChange
2238};
2239
2240
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002241static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002242 // The error epsilon was empirically derived; worse case round rects
2243 // with a mid point outset by 2x float epsilon in tests had an error
2244 // of 12.
2245 const int epsilon = 16;
2246 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2247 return false;
2248 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002249 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002250 int aBits = SkFloatAs2sCompliment(compA);
2251 int bBits = SkFloatAs2sCompliment(compB);
2252 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002253}
2254
robertphillipsc506e302014-09-16 09:43:31 -07002255static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2256 const SkVector& lastVec, const SkVector& curVec) {
2257 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2258
2259 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2260 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2261 largest = SkTMax(largest, -smallest);
2262
2263 if (!almost_equal(largest, largest + cross)) {
2264 int sign = SkScalarSignAsInt(cross);
2265 if (sign) {
2266 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2267 }
2268 }
2269
2270 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2271 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2272 lastVec.dot(curVec) < 0.0f) {
2273 return kBackwards_DirChange;
2274 }
2275
2276 return kStraight_DirChange;
2277}
2278
reed@google.com04863fa2011-05-15 04:08:24 +00002279// only valid for a single contour
2280struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002281 Convexicator()
2282 : fPtCount(0)
2283 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002284 , fDirection(SkPath::kUnknown_Direction)
2285 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002286 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002287 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002288 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002289 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002290 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002291 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002292
2293 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002294 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 }
2296
2297 SkPath::Convexity getConvexity() const { return fConvexity; }
2298
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299 /** The direction returned is only valid if the path is determined convex */
2300 SkPath::Direction getDirection() const { return fDirection; }
2301
reed@google.com04863fa2011-05-15 04:08:24 +00002302 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002303 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002304 return;
2305 }
2306
2307 if (0 == fPtCount) {
2308 fCurrPt = pt;
2309 ++fPtCount;
2310 } else {
2311 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002312 SkScalar lengthSqd = vec.lengthSqd();
2313 if (!SkScalarIsFinite(lengthSqd)) {
2314 fIsFinite = false;
2315 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002316 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002317 fCurrPt = pt;
2318 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002319 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002320 } else {
2321 SkASSERT(fPtCount > 2);
2322 this->addVec(vec);
2323 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002324
reed@google.com85b6e392011-05-15 20:25:17 +00002325 int sx = sign(vec.fX);
2326 int sy = sign(vec.fY);
2327 fDx += (sx != fSx);
2328 fDy += (sy != fSy);
2329 fSx = sx;
2330 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002331
reed@google.com85b6e392011-05-15 20:25:17 +00002332 if (fDx > 3 || fDy > 3) {
2333 fConvexity = SkPath::kConcave_Convexity;
2334 }
reed@google.com04863fa2011-05-15 04:08:24 +00002335 }
2336 }
2337 }
2338
2339 void close() {
2340 if (fPtCount > 2) {
2341 this->addVec(fFirstVec);
2342 }
2343 }
2344
caryclarkd3d1a982014-12-08 04:57:38 -08002345 bool isFinite() const {
2346 return fIsFinite;
2347 }
2348
reed@google.com04863fa2011-05-15 04:08:24 +00002349private:
2350 void addVec(const SkVector& vec) {
2351 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002352 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2353 switch (dir) {
2354 case kLeft_DirChange: // fall through
2355 case kRight_DirChange:
2356 if (kInvalid_DirChange == fExpectedDir) {
2357 fExpectedDir = dir;
2358 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2359 : SkPath::kCCW_Direction;
2360 } else if (dir != fExpectedDir) {
2361 fConvexity = SkPath::kConcave_Convexity;
2362 fDirection = SkPath::kUnknown_Direction;
2363 }
2364 fLastVec = vec;
2365 break;
2366 case kStraight_DirChange:
2367 break;
2368 case kBackwards_DirChange:
2369 fLastVec = vec;
2370 break;
2371 case kInvalid_DirChange:
2372 SkFAIL("Use of invalid direction change flag");
2373 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002374 }
2375 }
2376
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002377 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002379 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2380 // value with the current vec is deemed to be of a significant value.
2381 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002382 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002383 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002384 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;
caryclarkd3d1a982014-12-08 04:57:38 -08002387 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002388};
2389
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002390SkPath::Convexity SkPath::internalGetConvexity() const {
2391 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002392 SkPoint pts[4];
2393 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002394 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002395
2396 int contourCount = 0;
2397 int count;
2398 Convexicator state;
2399
caryclarkd3d1a982014-12-08 04:57:38 -08002400 if (!isFinite()) {
2401 return kUnknown_Convexity;
2402 }
reed@google.com04863fa2011-05-15 04:08:24 +00002403 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2404 switch (verb) {
2405 case kMove_Verb:
2406 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002407 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002408 return kConcave_Convexity;
2409 }
2410 pts[1] = pts[0];
2411 count = 1;
2412 break;
2413 case kLine_Verb: count = 1; break;
2414 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002415 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002416 case kCubic_Verb: count = 3; break;
2417 case kClose_Verb:
2418 state.close();
2419 count = 0;
2420 break;
2421 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002422 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002423 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002424 return kConcave_Convexity;
2425 }
2426
2427 for (int i = 1; i <= count; i++) {
2428 state.addPt(pts[i]);
2429 }
2430 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002431 if (!state.isFinite()) {
2432 return kUnknown_Convexity;
2433 }
reed@google.com04863fa2011-05-15 04:08:24 +00002434 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002436 return kConcave_Convexity;
2437 }
2438 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002439 fConvexity = state.getConvexity();
2440 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2441 fDirection = state.getDirection();
2442 }
2443 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002444}
reed@google.com69a99432012-01-10 18:00:10 +00002445
2446///////////////////////////////////////////////////////////////////////////////
2447
2448class ContourIter {
2449public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002450 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002451
2452 bool done() const { return fDone; }
2453 // if !done() then these may be called
2454 int count() const { return fCurrPtCount; }
2455 const SkPoint* pts() const { return fCurrPt; }
2456 void next();
2457
2458private:
2459 int fCurrPtCount;
2460 const SkPoint* fCurrPt;
2461 const uint8_t* fCurrVerb;
2462 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002463 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002464 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002465 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002466};
2467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002468ContourIter::ContourIter(const SkPathRef& pathRef) {
2469 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002470 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002471 fCurrPt = pathRef.points();
2472 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002473 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002474 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002475 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002476 this->next();
2477}
2478
2479void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002480 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002481 fDone = true;
2482 }
2483 if (fDone) {
2484 return;
2485 }
2486
2487 // skip pts of prev contour
2488 fCurrPt += fCurrPtCount;
2489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002490 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002491 int ptCount = 1; // moveTo
2492 const uint8_t* verbs = fCurrVerb;
2493
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002494 for (--verbs; verbs > fStopVerbs; --verbs) {
2495 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002496 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002497 goto CONTOUR_END;
2498 case SkPath::kLine_Verb:
2499 ptCount += 1;
2500 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002501 case SkPath::kConic_Verb:
2502 fCurrConicWeight += 1;
2503 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002504 case SkPath::kQuad_Verb:
2505 ptCount += 2;
2506 break;
2507 case SkPath::kCubic_Verb:
2508 ptCount += 3;
2509 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002510 case SkPath::kClose_Verb:
2511 break;
2512 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002513 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002514 break;
2515 }
2516 }
2517CONTOUR_END:
2518 fCurrPtCount = ptCount;
2519 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002520 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002521}
2522
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002523// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002524static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002525 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2526 // We may get 0 when the above subtracts underflow. We expect this to be
2527 // very rare and lazily promote to double.
2528 if (0 == cross) {
2529 double p0x = SkScalarToDouble(p0.fX);
2530 double p0y = SkScalarToDouble(p0.fY);
2531
2532 double p1x = SkScalarToDouble(p1.fX);
2533 double p1y = SkScalarToDouble(p1.fY);
2534
2535 double p2x = SkScalarToDouble(p2.fX);
2536 double p2y = SkScalarToDouble(p2.fY);
2537
2538 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2539 (p1y - p0y) * (p2x - p0x));
2540
2541 }
2542 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002543}
2544
reed@google.comc1ea60a2012-01-31 15:15:36 +00002545// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002546static int find_max_y(const SkPoint pts[], int count) {
2547 SkASSERT(count > 0);
2548 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002549 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002550 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002551 SkScalar y = pts[i].fY;
2552 if (y > max) {
2553 max = y;
2554 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002555 }
2556 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002557 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002558}
2559
reed@google.comcabaf1d2012-01-11 21:03:05 +00002560static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2561 int i = index;
2562 for (;;) {
2563 i = (i + inc) % n;
2564 if (i == index) { // we wrapped around, so abort
2565 break;
2566 }
2567 if (pts[index] != pts[i]) { // found a different point, success!
2568 break;
2569 }
2570 }
2571 return i;
2572}
2573
reed@google.comc1ea60a2012-01-31 15:15:36 +00002574/**
2575 * Starting at index, and moving forward (incrementing), find the xmin and
2576 * xmax of the contiguous points that have the same Y.
2577 */
2578static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2579 int* maxIndexPtr) {
2580 const SkScalar y = pts[index].fY;
2581 SkScalar min = pts[index].fX;
2582 SkScalar max = min;
2583 int minIndex = index;
2584 int maxIndex = index;
2585 for (int i = index + 1; i < n; ++i) {
2586 if (pts[i].fY != y) {
2587 break;
2588 }
2589 SkScalar x = pts[i].fX;
2590 if (x < min) {
2591 min = x;
2592 minIndex = i;
2593 } else if (x > max) {
2594 max = x;
2595 maxIndex = i;
2596 }
2597 }
2598 *maxIndexPtr = maxIndex;
2599 return minIndex;
2600}
2601
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002602static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002603 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002604}
2605
reed@google.comac8543f2012-01-30 20:51:25 +00002606/*
2607 * We loop through all contours, and keep the computed cross-product of the
2608 * contour that contained the global y-max. If we just look at the first
2609 * contour, we may find one that is wound the opposite way (correctly) since
2610 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2611 * that is outer most (or at least has the global y-max) before we can consider
2612 * its cross product.
2613 */
reed@google.com69a99432012-01-10 18:00:10 +00002614bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002615 if (kUnknown_Direction != fDirection) {
2616 *dir = static_cast<Direction>(fDirection);
2617 return true;
2618 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002619
2620 // don't want to pay the cost for computing this if it
2621 // is unknown, so we don't call isConvex()
2622 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2623 SkASSERT(kUnknown_Direction == fDirection);
2624 *dir = static_cast<Direction>(fDirection);
2625 return false;
2626 }
reed@google.com69a99432012-01-10 18:00:10 +00002627
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002628 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002629
reed@google.comac8543f2012-01-30 20:51:25 +00002630 // initialize with our logical y-min
2631 SkScalar ymax = this->getBounds().fTop;
2632 SkScalar ymaxCross = 0;
2633
reed@google.com69a99432012-01-10 18:00:10 +00002634 for (; !iter.done(); iter.next()) {
2635 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002636 if (n < 3) {
2637 continue;
2638 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002639
reed@google.comcabaf1d2012-01-11 21:03:05 +00002640 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002641 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002642 int index = find_max_y(pts, n);
2643 if (pts[index].fY < ymax) {
2644 continue;
2645 }
2646
2647 // If there is more than 1 distinct point at the y-max, we take the
2648 // x-min and x-max of them and just subtract to compute the dir.
2649 if (pts[(index + 1) % n].fY == pts[index].fY) {
2650 int maxIndex;
2651 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2652 if (minIndex == maxIndex) {
2653 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002654 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002655 SkASSERT(pts[minIndex].fY == pts[index].fY);
2656 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2657 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2658 // we just subtract the indices, and let that auto-convert to
2659 // SkScalar, since we just want - or + to signal the direction.
2660 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002661 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002662 TRY_CROSSPROD:
2663 // Find a next and prev index to use for the cross-product test,
2664 // but we try to find pts that form non-zero vectors from pts[index]
2665 //
2666 // Its possible that we can't find two non-degenerate vectors, so
2667 // we have to guard our search (e.g. all the pts could be in the
2668 // same place).
2669
2670 // we pass n - 1 instead of -1 so we don't foul up % operator by
2671 // passing it a negative LH argument.
2672 int prev = find_diff_pt(pts, index, n, n - 1);
2673 if (prev == index) {
2674 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002675 continue;
2676 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002677 int next = find_diff_pt(pts, index, n, 1);
2678 SkASSERT(next != index);
2679 cross = cross_prod(pts[prev], pts[index], pts[next]);
2680 // if we get a zero and the points are horizontal, then we look at the spread in
2681 // x-direction. We really should continue to walk away from the degeneracy until
2682 // there is a divergence.
2683 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2684 // construct the subtract so we get the correct Direction below
2685 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002686 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002688
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002689 if (cross) {
2690 // record our best guess so far
2691 ymax = pts[index].fY;
2692 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002693 }
2694 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002695 if (ymaxCross) {
2696 crossToDir(ymaxCross, dir);
2697 fDirection = *dir;
2698 return true;
2699 } else {
2700 return false;
2701 }
reed@google.comac8543f2012-01-30 20:51:25 +00002702}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002703
2704///////////////////////////////////////////////////////////////////////////////
2705
2706static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2707 SkScalar D, SkScalar t) {
2708 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2709}
2710
2711static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2712 SkScalar t) {
2713 SkScalar A = c3 + 3*(c1 - c2) - c0;
2714 SkScalar B = 3*(c2 - c1 - c1 + c0);
2715 SkScalar C = 3*(c1 - c0);
2716 SkScalar D = c0;
2717 return eval_cubic_coeff(A, B, C, D, t);
2718}
2719
2720/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2721 t value such that cubic(t) = target
2722 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002723static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 SkScalar target, SkScalar* t) {
2725 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2726 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 SkScalar D = c0 - target;
2729 SkScalar A = c3 + 3*(c1 - c2) - c0;
2730 SkScalar B = 3*(c2 - c1 - c1 + c0);
2731 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002733 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2734 SkScalar minT = 0;
2735 SkScalar maxT = SK_Scalar1;
2736 SkScalar mid;
2737 int i;
2738 for (i = 0; i < 16; i++) {
2739 mid = SkScalarAve(minT, maxT);
2740 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2741 if (delta < 0) {
2742 minT = mid;
2743 delta = -delta;
2744 } else {
2745 maxT = mid;
2746 }
2747 if (delta < TOLERANCE) {
2748 break;
2749 }
2750 }
2751 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752}
2753
2754template <size_t N> static void find_minmax(const SkPoint pts[],
2755 SkScalar* minPtr, SkScalar* maxPtr) {
2756 SkScalar min, max;
2757 min = max = pts[0].fX;
2758 for (size_t i = 1; i < N; ++i) {
2759 min = SkMinScalar(min, pts[i].fX);
2760 max = SkMaxScalar(max, pts[i].fX);
2761 }
2762 *minPtr = min;
2763 *maxPtr = max;
2764}
2765
2766static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2767 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 int dir = 1;
2770 if (pts[0].fY > pts[3].fY) {
2771 storage[0] = pts[3];
2772 storage[1] = pts[2];
2773 storage[2] = pts[1];
2774 storage[3] = pts[0];
2775 pts = storage;
2776 dir = -1;
2777 }
2778 if (y < pts[0].fY || y >= pts[3].fY) {
2779 return 0;
2780 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002781
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002782 // quickreject or quickaccept
2783 SkScalar min, max;
2784 find_minmax<4>(pts, &min, &max);
2785 if (x < min) {
2786 return 0;
2787 }
2788 if (x > max) {
2789 return dir;
2790 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002791
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002793 SkScalar t;
2794 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2795 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 +00002796 return xt < x ? dir : 0;
2797}
2798
2799static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2800 SkPoint dst[10];
2801 int n = SkChopCubicAtYExtrema(pts, dst);
2802 int w = 0;
2803 for (int i = 0; i <= n; ++i) {
2804 w += winding_mono_cubic(&dst[i * 3], x, y);
2805 }
2806 return w;
2807}
2808
2809static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2810 SkScalar y0 = pts[0].fY;
2811 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 int dir = 1;
2814 if (y0 > y2) {
2815 SkTSwap(y0, y2);
2816 dir = -1;
2817 }
2818 if (y < y0 || y >= y2) {
2819 return 0;
2820 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 // bounds check on X (not required. is it faster?)
2823#if 0
2824 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2825 return 0;
2826 }
2827#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 SkScalar roots[2];
2830 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2831 2 * (pts[1].fY - pts[0].fY),
2832 pts[0].fY - y,
2833 roots);
2834 SkASSERT(n <= 1);
2835 SkScalar xt;
2836 if (0 == n) {
2837 SkScalar mid = SkScalarAve(y0, y2);
2838 // Need [0] and [2] if dir == 1
2839 // and [2] and [0] if dir == -1
2840 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2841 } else {
2842 SkScalar t = roots[0];
2843 SkScalar C = pts[0].fX;
2844 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2845 SkScalar B = 2 * (pts[1].fX - C);
2846 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2847 }
2848 return xt < x ? dir : 0;
2849}
2850
2851static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2852 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2853 if (y0 == y1) {
2854 return true;
2855 }
2856 if (y0 < y1) {
2857 return y1 <= y2;
2858 } else {
2859 return y1 >= y2;
2860 }
2861}
2862
2863static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2864 SkPoint dst[5];
2865 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2868 n = SkChopQuadAtYExtrema(pts, dst);
2869 pts = dst;
2870 }
2871 int w = winding_mono_quad(pts, x, y);
2872 if (n > 0) {
2873 w += winding_mono_quad(&pts[2], x, y);
2874 }
2875 return w;
2876}
2877
2878static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2879 SkScalar x0 = pts[0].fX;
2880 SkScalar y0 = pts[0].fY;
2881 SkScalar x1 = pts[1].fX;
2882 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002883
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002885
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 int dir = 1;
2887 if (y0 > y1) {
2888 SkTSwap(y0, y1);
2889 dir = -1;
2890 }
2891 if (y < y0 || y >= y1) {
2892 return 0;
2893 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2896 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002897
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002898 if (SkScalarSignAsInt(cross) == dir) {
2899 dir = 0;
2900 }
2901 return dir;
2902}
2903
reed@google.com4db592c2013-10-30 17:39:43 +00002904static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2905 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2906}
2907
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002908bool SkPath::contains(SkScalar x, SkScalar y) const {
2909 bool isInverse = this->isInverseFillType();
2910 if (this->isEmpty()) {
2911 return isInverse;
2912 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002913
reed@google.com4db592c2013-10-30 17:39:43 +00002914 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002915 return isInverse;
2916 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002917
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002918 SkPath::Iter iter(*this, true);
2919 bool done = false;
2920 int w = 0;
2921 do {
2922 SkPoint pts[4];
2923 switch (iter.next(pts, false)) {
2924 case SkPath::kMove_Verb:
2925 case SkPath::kClose_Verb:
2926 break;
2927 case SkPath::kLine_Verb:
2928 w += winding_line(pts, x, y);
2929 break;
2930 case SkPath::kQuad_Verb:
2931 w += winding_quad(pts, x, y);
2932 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002933 case SkPath::kConic_Verb:
2934 SkASSERT(0);
2935 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 case SkPath::kCubic_Verb:
2937 w += winding_cubic(pts, x, y);
2938 break;
2939 case SkPath::kDone_Verb:
2940 done = true;
2941 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002942 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002943 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002944
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 switch (this->getFillType()) {
2946 case SkPath::kEvenOdd_FillType:
2947 case SkPath::kInverseEvenOdd_FillType:
2948 w &= 1;
2949 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002950 default:
2951 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 }
2953 return SkToBool(w);
2954}