blob: 33a0617da95736c79b3d009e4846391f5e9d9425 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
41 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
42 }
43
44 ~SkAutoDisableDirectionCheck() {
45 fPath->fDirection = fSaved;
46 }
47
48private:
49 SkPath* fPath;
50 SkPath::Direction fSaved;
51};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000129 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000131 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132#endif
133{
134 this->resetFields();
135}
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;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181}
182
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000183bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000184 // note: don't need to look at isConvex or bounds, since just comparing the
185 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000186 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000187 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190void SkPath::swap(SkPath& that) {
191 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 if (this != &that) {
194 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000195 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
198 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000199#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000200 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
201#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000205static inline bool check_edge_against_rect(const SkPoint& p0,
206 const SkPoint& p1,
207 const SkRect& rect,
208 SkPath::Direction dir) {
209 const SkPoint* edgeBegin;
210 SkVector v;
211 if (SkPath::kCW_Direction == dir) {
212 v = p1 - p0;
213 edgeBegin = &p0;
214 } else {
215 v = p0 - p1;
216 edgeBegin = &p1;
217 }
218 if (v.fX || v.fY) {
219 // check the cross product of v with the vec from edgeBegin to each rect corner
220 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
221 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
222 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
223 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
224 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
225 return false;
226 }
227 }
228 return true;
229}
230
231bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
232 // This only handles non-degenerate convex paths currently.
233 if (kConvex_Convexity != this->getConvexity()) {
234 return false;
235 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237 Direction direction;
238 if (!this->cheapComputeDirection(&direction)) {
239 return false;
240 }
241
242 SkPoint firstPt;
243 SkPoint prevPt;
244 RawIter iter(*this);
245 SkPath::Verb verb;
246 SkPoint pts[4];
247 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000248 SkDEBUGCODE(int segmentCount = 0;)
249 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250
251 while ((verb = iter.next(pts)) != kDone_Verb) {
252 int nextPt = -1;
253 switch (verb) {
254 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000256 SkDEBUGCODE(++moveCnt);
257 firstPt = prevPt = pts[0];
258 break;
259 case kLine_Verb:
260 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 break;
264 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000265 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkASSERT(moveCnt && !closeCount);
267 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 nextPt = 2;
269 break;
270 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkASSERT(moveCnt && !closeCount);
272 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273 nextPt = 3;
274 break;
275 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000276 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 break;
278 default:
279 SkDEBUGFAIL("unknown verb");
280 }
281 if (-1 != nextPt) {
282 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
283 return false;
284 }
285 prevPt = pts[nextPt];
286 }
287 }
288
289 return check_edge_against_rect(prevPt, firstPt, rect, direction);
290}
291
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000292uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000293 uint32_t genID = fPathRef->genID();
294#ifdef SK_BUILD_FOR_ANDROID
295 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
296 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
297#endif
298 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000299}
djsollen@google.come63793a2012-03-21 15:39:03 +0000300
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000302const SkPath* SkPath::getSourcePath() const {
303 return fSourcePath;
304}
305
306void SkPath::setSourcePath(const SkPath* path) {
307 fSourcePath = path;
308}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000309#endif
310
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311void SkPath::reset() {
312 SkDEBUGCODE(this->validate();)
313
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000314 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000315 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316}
317
318void SkPath::rewind() {
319 SkDEBUGCODE(this->validate();)
320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000322 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323}
324
reed@google.com7e6c4d12012-05-10 14:05:43 +0000325bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000326 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000327
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000328 if (2 == verbCount) {
329 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
330 if (kLine_Verb == fPathRef->atVerb(1)) {
331 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000332 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000334 line[0] = pts[0];
335 line[1] = pts[1];
336 }
337 return true;
338 }
339 }
340 return false;
341}
342
caryclark@google.comf1316942011-07-26 19:54:45 +0000343/*
344 Determines if path is a rect by keeping track of changes in direction
345 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347 The direction is computed such that:
348 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000349 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000350 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000351 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000352
caryclark@google.comf1316942011-07-26 19:54:45 +0000353A rectangle cycles up/right/down/left or up/left/down/right.
354
355The test fails if:
356 The path is closed, and followed by a line.
357 A second move creates a new endpoint.
358 A diagonal line is parsed.
359 There's more than four changes of direction.
360 There's a discontinuity on the line (e.g., a move in the middle)
361 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000362 The path contains a quadratic or cubic.
363 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000364 *The rectangle doesn't complete a cycle.
365 *The final point isn't equal to the first point.
366
367 *These last two conditions we relax if we have a 3-edge path that would
368 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370It's OK if the path has:
371 Several colinear line segments composing a rectangle side.
372 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374The direction takes advantage of the corners found since opposite sides
375must travel in opposite directions.
376
377FIXME: Allow colinear quads and cubics to be treated like lines.
378FIXME: If the API passes fill-only, return true if the filled stroke
379 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000380
381 first,last,next direction state-machine:
382 0x1 is set if the segment is horizontal
383 0x2 is set if the segment is moving to the right or down
384 thus:
385 two directions are opposites iff (dirA ^ dirB) == 0x2
386 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000389static int rect_make_dir(SkScalar dx, SkScalar dy) {
390 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
391}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
393 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 int corners = 0;
395 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000396 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000397 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000398 first.set(0, 0);
399 last.set(0, 0);
400 int firstDirection = 0;
401 int lastDirection = 0;
402 int nextDirection = 0;
403 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000405 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
407 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 savePts = pts;
410 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 autoClose = true;
412 case kLine_Verb: {
413 SkScalar left = last.fX;
414 SkScalar top = last.fY;
415 SkScalar right = pts->fX;
416 SkScalar bottom = pts->fY;
417 ++pts;
418 if (left != right && top != bottom) {
419 return false; // diagonal
420 }
421 if (left == right && top == bottom) {
422 break; // single point on side OK
423 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 if (0 == corners) {
426 firstDirection = nextDirection;
427 first = last;
428 last = pts[-1];
429 corners = 1;
430 closedOrMoved = false;
431 break;
432 }
433 if (closedOrMoved) {
434 return false; // closed followed by a line
435 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000436 if (autoClose && nextDirection == firstDirection) {
437 break; // colinear with first
438 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 closedOrMoved = autoClose;
440 if (lastDirection != nextDirection) {
441 if (++corners > 4) {
442 return false; // too many direction changes
443 }
444 }
445 last = pts[-1];
446 if (lastDirection == nextDirection) {
447 break; // colinear segment
448 }
449 // Possible values for corners are 2, 3, and 4.
450 // When corners == 3, nextDirection opposes firstDirection.
451 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
454 if ((directionCycle ^ turn) != nextDirection) {
455 return false; // direction didn't follow cycle
456 }
457 break;
458 }
459 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000460 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 case kCubic_Verb:
462 return false; // quadratic, cubic not allowed
463 case kMove_Verb:
464 last = *pts++;
465 closedOrMoved = true;
466 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000467 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000468 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000469 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 lastDirection = nextDirection;
473 }
474 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000476 if (!result) {
477 // check if we are just an incomplete rectangle, in which case we can
478 // return true, but not claim to be closed.
479 // e.g.
480 // 3 sided rectangle
481 // 4 sided but the last edge is not long enough to reach the start
482 //
483 SkScalar closeX = first.x() - last.x();
484 SkScalar closeY = first.y() - last.y();
485 if (closeX && closeY) {
486 return false; // we're diagonal, abort (can we ever reach this?)
487 }
488 int closeDirection = rect_make_dir(closeX, closeY);
489 // make sure the close-segment doesn't double-back on itself
490 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
491 result = true;
492 autoClose = false; // we are not closed
493 }
494 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000507SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
508 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000509 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
510 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000511 bool isClosed = false;
512 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
513}
514
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000516 SkDEBUGCODE(this->validate();)
517 int currVerb = 0;
518 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000519 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
520 if (result && rect) {
521 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000522 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000523 return result;
524}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000525
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000526bool SkPath::isRect(bool* isClosed, Direction* direction) const {
527 SkDEBUGCODE(this->validate();)
528 int currVerb = 0;
529 const SkPoint* pts = fPathRef->points();
530 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000531}
532
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000533bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 SkDEBUGCODE(this->validate();)
535 int currVerb = 0;
536 const SkPoint* pts = fPathRef->points();
537 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 Direction testDirs[2];
539 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000540 return false;
541 }
542 const SkPoint* last = pts;
543 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[0].set(first, SkToS32(last - first));
546 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 if (testRects[0].contains(testRects[1])) {
548 if (rects) {
549 rects[0] = testRects[0];
550 rects[1] = testRects[1];
551 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000552 if (dirs) {
553 dirs[0] = testDirs[0];
554 dirs[1] = testDirs[1];
555 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 return true;
557 }
558 if (testRects[1].contains(testRects[0])) {
559 if (rects) {
560 rects[0] = testRects[1];
561 rects[1] = testRects[0];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[1];
565 dirs[1] = testDirs[0];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 }
570 return false;
571}
572
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000573int SkPath::countPoints() const {
574 return fPathRef->countPoints();
575}
576
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000577int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 SkDEBUGCODE(this->validate();)
579
580 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000581 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 int count = SkMin32(max, fPathRef->countPoints());
583 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
584 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585}
586
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000587SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
589 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590 }
591 return SkPoint::Make(0, 0);
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countVerbs() const {
595 return fPathRef->countVerbs();
596}
597
598static inline void copy_verbs_reverse(uint8_t* inorderDst,
599 const uint8_t* reversedSrc,
600 int count) {
601 for (int i = 0; i < count; ++i) {
602 inorderDst[i] = reversedSrc[~i];
603 }
604}
605
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606int SkPath::getVerbs(uint8_t dst[], int max) const {
607 SkDEBUGCODE(this->validate();)
608
609 SkASSERT(max >= 0);
610 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = SkMin32(max, fPathRef->countVerbs());
612 copy_verbs_reverse(dst, fPathRef->verbs(), count);
613 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000614}
615
reed@google.com294dd7b2011-10-11 11:58:32 +0000616bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 SkDEBUGCODE(this->validate();)
618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000619 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (count > 0) {
621 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000624 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 if (lastPt) {
627 lastPt->set(0, 0);
628 }
629 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630}
631
632void SkPath::setLastPt(SkScalar x, SkScalar y) {
633 SkDEBUGCODE(this->validate();)
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 if (count == 0) {
637 this->moveTo(x, y);
638 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 SkPathRef::Editor ed(&fPathRef);
640 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 }
642}
643
reed@google.com04863fa2011-05-15 04:08:24 +0000644void SkPath::setConvexity(Convexity c) {
645 if (fConvexity != c) {
646 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000647 }
648}
649
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650//////////////////////////////////////////////////////////////////////////////
651// Construction methods
652
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000653#define DIRTY_AFTER_EDIT \
654 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000655 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000656 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000657 } while (0)
658
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659void SkPath::incReserve(U16CPU inc) {
660 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 SkDEBUGCODE(this->validate();)
663}
664
665void SkPath::moveTo(SkScalar x, SkScalar y) {
666 SkDEBUGCODE(this->validate();)
667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000670 // remember our index
671 fLastMoveToIndex = fPathRef->countPoints();
672
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700674
675 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676}
677
678void SkPath::rMoveTo(SkScalar x, SkScalar y) {
679 SkPoint pt;
680 this->getLastPt(&pt);
681 this->moveTo(pt.fX + x, pt.fY + y);
682}
683
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000684void SkPath::injectMoveToIfNeeded() {
685 if (fLastMoveToIndex < 0) {
686 SkScalar x, y;
687 if (fPathRef->countVerbs() == 0) {
688 x = y = 0;
689 } else {
690 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
691 x = pt.fX;
692 y = pt.fY;
693 }
694 this->moveTo(x, y);
695 }
696}
697
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698void SkPath::lineTo(SkScalar x, SkScalar y) {
699 SkDEBUGCODE(this->validate();)
700
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000701 this->injectMoveToIfNeeded();
702
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000703 SkPathRef::Editor ed(&fPathRef);
704 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705
reed@google.comb54455e2011-05-16 14:16:04 +0000706 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707}
708
709void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000710 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711 SkPoint pt;
712 this->getLastPt(&pt);
713 this->lineTo(pt.fX + x, pt.fY + y);
714}
715
716void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
717 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000718
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000719 this->injectMoveToIfNeeded();
720
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 SkPathRef::Editor ed(&fPathRef);
722 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 pts[0].set(x1, y1);
724 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000725
reed@google.comb54455e2011-05-16 14:16:04 +0000726 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727}
728
729void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000730 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731 SkPoint pt;
732 this->getLastPt(&pt);
733 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
734}
735
reed@google.com277c3f82013-05-31 15:17:50 +0000736void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
737 SkScalar w) {
738 // check for <= 0 or NaN with this test
739 if (!(w > 0)) {
740 this->lineTo(x2, y2);
741 } else if (!SkScalarIsFinite(w)) {
742 this->lineTo(x1, y1);
743 this->lineTo(x2, y2);
744 } else if (SK_Scalar1 == w) {
745 this->quadTo(x1, y1, x2, y2);
746 } else {
747 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000748
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000749 this->injectMoveToIfNeeded();
750
reed@google.com277c3f82013-05-31 15:17:50 +0000751 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000752 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000753 pts[0].set(x1, y1);
754 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000755
reed@google.com277c3f82013-05-31 15:17:50 +0000756 DIRTY_AFTER_EDIT;
757 }
758}
759
760void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
761 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000762 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000763 SkPoint pt;
764 this->getLastPt(&pt);
765 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
766}
767
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
769 SkScalar x3, SkScalar y3) {
770 SkDEBUGCODE(this->validate();)
771
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000772 this->injectMoveToIfNeeded();
773
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000774 SkPathRef::Editor ed(&fPathRef);
775 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 pts[0].set(x1, y1);
777 pts[1].set(x2, y2);
778 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779
reed@google.comb54455e2011-05-16 14:16:04 +0000780 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781}
782
783void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
784 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000785 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 SkPoint pt;
787 this->getLastPt(&pt);
788 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
789 pt.fX + x3, pt.fY + y3);
790}
791
792void SkPath::close() {
793 SkDEBUGCODE(this->validate();)
794
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 case kLine_Verb:
799 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000800 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 case kMove_Verb: {
803 SkPathRef::Editor ed(&fPathRef);
804 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000806 }
reed@google.com277c3f82013-05-31 15:17:50 +0000807 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000808 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000809 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000810 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000811 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000812 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 }
814 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000815
816 // signal that we need a moveTo to follow us (unless we're done)
817#if 0
818 if (fLastMoveToIndex >= 0) {
819 fLastMoveToIndex = ~fLastMoveToIndex;
820 }
821#else
822 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
823#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824}
825
826///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000827
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000828static void assert_known_direction(int dir) {
829 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
830}
831
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832void SkPath::addRect(const SkRect& rect, Direction dir) {
833 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
834}
835
836void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
837 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000838 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000839 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
840 SkAutoDisableDirectionCheck addc(this);
841
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
843
844 this->incReserve(5);
845
846 this->moveTo(left, top);
847 if (dir == kCCW_Direction) {
848 this->lineTo(left, bottom);
849 this->lineTo(right, bottom);
850 this->lineTo(right, top);
851 } else {
852 this->lineTo(right, top);
853 this->lineTo(right, bottom);
854 this->lineTo(left, bottom);
855 }
856 this->close();
857}
858
reed@google.com744faba2012-05-29 19:54:52 +0000859void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
860 SkDEBUGCODE(this->validate();)
861 if (count <= 0) {
862 return;
863 }
864
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000865 fLastMoveToIndex = fPathRef->countPoints();
866
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000867 // +close makes room for the extra kClose_Verb
868 SkPathRef::Editor ed(&fPathRef, count+close, count);
869
870 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000871 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000872 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
873 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000874 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000875
reed@google.com744faba2012-05-29 19:54:52 +0000876 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000877 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000878 }
879
reed@google.com744faba2012-05-29 19:54:52 +0000880 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000882}
883
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000884#include "SkGeometry.h"
885
886static int build_arc_points(const SkRect& oval, SkScalar startAngle,
887 SkScalar sweepAngle,
888 SkPoint pts[kSkBuildQuadArcStorage]) {
889
890 if (0 == sweepAngle &&
891 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
892 // Chrome uses this path to move into and out of ovals. If not
893 // treated as a special case the moves can distort the oval's
894 // bounding box (and break the circle special case).
895 pts[0].set(oval.fRight, oval.centerY());
896 return 1;
897 } else if (0 == oval.width() && 0 == oval.height()) {
898 // Chrome will sometimes create 0 radius round rects. Having degenerate
899 // quad segments in the path prevents the path from being recognized as
900 // a rect.
901 // TODO: optimizing the case where only one of width or height is zero
902 // should also be considered. This case, however, doesn't seem to be
903 // as common as the single point case.
904 pts[0].set(oval.fRight, oval.fTop);
905 return 1;
906 }
907
908 SkVector start, stop;
909
910 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
911 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
912 &stop.fX);
913
914 /* If the sweep angle is nearly (but less than) 360, then due to precision
915 loss in radians-conversion and/or sin/cos, we may end up with coincident
916 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
917 of drawing a nearly complete circle (good).
918 e.g. canvas.drawArc(0, 359.99, ...)
919 -vs- canvas.drawArc(0, 359.9, ...)
920 We try to detect this edge case, and tweak the stop vector
921 */
922 if (start == stop) {
923 SkScalar sw = SkScalarAbs(sweepAngle);
924 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
925 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
926 // make a guess at a tiny angle (in radians) to tweak by
927 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
928 // not sure how much will be enough, so we use a loop
929 do {
930 stopRad -= deltaRad;
931 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
932 } while (start == stop);
933 }
934 }
935
936 SkMatrix matrix;
937
938 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
939 matrix.postTranslate(oval.centerX(), oval.centerY());
940
941 return SkBuildQuadArc(start, stop,
942 sweepAngle > 0 ? kCW_SkRotationDirection :
943 kCCW_SkRotationDirection,
944 &matrix, pts);
945}
946
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000947void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000949 SkRRect rrect;
950 rrect.setRectRadii(rect, (const SkVector*) radii);
951 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952}
953
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000954/* The inline clockwise and counterclockwise round rect quad approximations
955 make it easier to see the symmetry patterns used by add corner quads.
956Clockwise corner value
957 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
958 path->quadTo(rect.fLeft, rect.fTop + offPtY,
959 rect.fLeft + midPtX, rect.fTop + midPtY);
960 path->quadTo(rect.fLeft + offPtX, rect.fTop,
961 rect.fLeft + rx, rect.fTop);
962
963 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
964 path->quadTo(rect.fRight - offPtX, rect.fTop,
965 rect.fRight - midPtX, rect.fTop + midPtY);
966 path->quadTo(rect.fRight, rect.fTop + offPtY,
967 rect.fRight, rect.fTop + ry);
968
969 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
970 path->quadTo(rect.fRight, rect.fBottom - offPtY,
971 rect.fRight - midPtX, rect.fBottom - midPtY);
972 path->quadTo(rect.fRight - offPtX, rect.fBottom,
973 rect.fRight - rx, rect.fBottom);
974
975 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
976 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
977 rect.fLeft + midPtX, rect.fBottom - midPtY);
978 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
979 rect.fLeft, rect.fBottom - ry);
980
981Counterclockwise
982 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
983 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
984 rect.fLeft + midPtX, rect.fBottom - midPtY);
985 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
986 rect.fLeft + rx, rect.fBottom);
987
988 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
989 path->quadTo(rect.fRight - offPtX, rect.fBottom,
990 rect.fRight - midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fRight, rect.fBottom - offPtY,
992 rect.fRight, rect.fBottom - ry);
993
994 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
995 path->quadTo(rect.fRight, rect.fTop + offPtY,
996 rect.fRight - midPtX, rect.fTop + midPtY);
997 path->quadTo(rect.fRight - offPtX, rect.fTop,
998 rect.fRight - rx, rect.fTop);
999
1000 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1001 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1002 rect.fLeft + midPtX, rect.fTop + midPtY);
1003 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1004 rect.fLeft, rect.fTop + ry);
1005*/
1006static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1007 SkRRect::Corner corner, SkPath::Direction dir) {
1008 const SkRect& rect = rrect.rect();
1009 const SkVector& radii = rrect.radii(corner);
1010 SkScalar rx = radii.fX;
1011 SkScalar ry = radii.fY;
1012 // The mid point of the quadratic arc approximation is half way between the two
1013 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001014 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1015 SkScalar midPtX = rx * mid;
1016 SkScalar midPtY = ry * mid;
1017 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1018 SkScalar offPtX = rx * control;
1019 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001020 static const int kCornerPts = 5;
1021 SkScalar xOff[kCornerPts];
1022 SkScalar yOff[kCornerPts];
1023
1024 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1025 SkASSERT(dir == SkPath::kCCW_Direction
1026 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1027 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1028 xOff[0] = xOff[1] = 0;
1029 xOff[2] = midPtX;
1030 xOff[3] = offPtX;
1031 xOff[4] = rx;
1032 yOff[0] = ry;
1033 yOff[1] = offPtY;
1034 yOff[2] = midPtY;
1035 yOff[3] = yOff[4] = 0;
1036 } else {
1037 xOff[0] = rx;
1038 xOff[1] = offPtX;
1039 xOff[2] = midPtX;
1040 xOff[3] = xOff[4] = 0;
1041 yOff[0] = yOff[1] = 0;
1042 yOff[2] = midPtY;
1043 yOff[3] = offPtY;
1044 yOff[4] = ry;
1045 }
1046 if ((corner - 1) & 2) {
1047 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1048 for (int i = 0; i < kCornerPts; ++i) {
1049 xOff[i] = rect.fLeft + xOff[i];
1050 }
1051 } else {
1052 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1053 for (int i = 0; i < kCornerPts; ++i) {
1054 xOff[i] = rect.fRight - xOff[i];
1055 }
1056 }
1057 if (corner < SkRRect::kLowerRight_Corner) {
1058 for (int i = 0; i < kCornerPts; ++i) {
1059 yOff[i] = rect.fTop + yOff[i];
1060 }
1061 } else {
1062 for (int i = 0; i < kCornerPts; ++i) {
1063 yOff[i] = rect.fBottom - yOff[i];
1064 }
1065 }
1066
1067 SkPoint lastPt;
1068 SkAssertResult(path->getLastPt(&lastPt));
1069 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1070 path->lineTo(xOff[0], yOff[0]);
1071 }
1072 if (rx || ry) {
1073 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1074 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1075 } else {
1076 path->lineTo(xOff[2], yOff[2]);
1077 path->lineTo(xOff[4], yOff[4]);
1078 }
1079}
1080
reed@google.com4ed0fb72012-12-12 20:48:18 +00001081void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001082 assert_known_direction(dir);
1083
1084 if (rrect.isEmpty()) {
1085 return;
1086 }
1087
reed@google.com4ed0fb72012-12-12 20:48:18 +00001088 const SkRect& bounds = rrect.getBounds();
1089
1090 if (rrect.isRect()) {
1091 this->addRect(bounds, dir);
1092 } else if (rrect.isOval()) {
1093 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001094 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001095 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001097 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001098 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001099
1100 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001101 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102 this->moveTo(bounds.fLeft,
1103 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1104 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1105 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1106 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1107 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001109 this->moveTo(bounds.fLeft,
1110 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1111 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1112 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1113 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1114 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115 }
1116 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001117 }
1118}
1119
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001120bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001121 int count = fPathRef->countVerbs();
1122 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1123 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001124 if (*verbs == kLine_Verb ||
1125 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001126 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001127 *verbs == kCubic_Verb) {
1128 return false;
1129 }
1130 ++verbs;
1131 }
1132 return true;
1133}
1134
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001135void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1136 Direction dir) {
1137 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001138
humper@google.com75e3ca12013-04-08 21:44:11 +00001139 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001140 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001141 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001142 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001143 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1144 return;
1145 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001146
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001147 SkRRect rrect;
1148 rrect.setRectXY(rect, rx, ry);
1149 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001150}
1151
reed@android.com8a1c16f2008-12-17 15:59:43 +00001152void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001153 assert_known_direction(dir);
1154
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001155 /* If addOval() is called after previous moveTo(),
1156 this path is still marked as an oval. This is used to
1157 fit into WebKit's calling sequences.
1158 We can't simply check isEmpty() in this case, as additional
1159 moveTo() would mark the path non empty.
1160 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001161 bool isOval = hasOnlyMoveTos();
1162 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001163 fDirection = dir;
1164 } else {
1165 fDirection = kUnknown_Direction;
1166 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001167
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001168 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001169
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170 SkAutoPathBoundsUpdate apbu(this, oval);
1171
1172 SkScalar cx = oval.centerX();
1173 SkScalar cy = oval.centerY();
1174 SkScalar rx = SkScalarHalf(oval.width());
1175 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1178 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1179 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1180 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1181
1182 /*
1183 To handle imprecision in computing the center and radii, we revert to
1184 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1185 to ensure that we don't exceed the oval's bounds *ever*, since we want
1186 to use oval for our fast-bounds, rather than have to recompute it.
1187 */
1188 const SkScalar L = oval.fLeft; // cx - rx
1189 const SkScalar T = oval.fTop; // cy - ry
1190 const SkScalar R = oval.fRight; // cx + rx
1191 const SkScalar B = oval.fBottom; // cy + ry
1192
1193 this->incReserve(17); // 8 quads + close
1194 this->moveTo(R, cy);
1195 if (dir == kCCW_Direction) {
1196 this->quadTo( R, cy - sy, cx + mx, cy - my);
1197 this->quadTo(cx + sx, T, cx , T);
1198 this->quadTo(cx - sx, T, cx - mx, cy - my);
1199 this->quadTo( L, cy - sy, L, cy );
1200 this->quadTo( L, cy + sy, cx - mx, cy + my);
1201 this->quadTo(cx - sx, B, cx , B);
1202 this->quadTo(cx + sx, B, cx + mx, cy + my);
1203 this->quadTo( R, cy + sy, R, cy );
1204 } else {
1205 this->quadTo( R, cy + sy, cx + mx, cy + my);
1206 this->quadTo(cx + sx, B, cx , B);
1207 this->quadTo(cx - sx, B, cx - mx, cy + my);
1208 this->quadTo( L, cy + sy, L, cy );
1209 this->quadTo( L, cy - sy, cx - mx, cy - my);
1210 this->quadTo(cx - sx, T, cx , T);
1211 this->quadTo(cx + sx, T, cx + mx, cy - my);
1212 this->quadTo( R, cy - sy, R, cy );
1213 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215
robertphillips@google.com466310d2013-12-03 16:43:54 +00001216 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001217
robertphillips@google.com466310d2013-12-03 16:43:54 +00001218 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001219}
1220
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1222 if (r > 0) {
1223 SkRect rect;
1224 rect.set(x - r, y - r, x + r, y + r);
1225 this->addOval(rect, dir);
1226 }
1227}
1228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1230 bool forceMoveTo) {
1231 if (oval.width() < 0 || oval.height() < 0) {
1232 return;
1233 }
1234
1235 SkPoint pts[kSkBuildQuadArcStorage];
1236 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1237 SkASSERT((count & 1) == 1);
1238
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001239 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 forceMoveTo = true;
1241 }
1242 this->incReserve(count);
1243 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1244 for (int i = 1; i < count; i += 2) {
1245 this->quadTo(pts[i], pts[i+1]);
1246 }
1247}
1248
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001249void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250 if (oval.isEmpty() || 0 == sweepAngle) {
1251 return;
1252 }
1253
1254 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1255
1256 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1257 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1258 return;
1259 }
1260
1261 SkPoint pts[kSkBuildQuadArcStorage];
1262 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1263
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001264 SkDEBUGCODE(this->validate();)
1265 SkASSERT(count & 1);
1266
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001267 fLastMoveToIndex = fPathRef->countPoints();
1268
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001269 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1270
1271 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1272 if (count > 1) {
1273 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1274 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001276
1277 DIRTY_AFTER_EDIT;
1278 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279}
1280
1281/*
1282 Need to handle the case when the angle is sharp, and our computed end-points
1283 for the arc go behind pt1 and/or p2...
1284*/
1285void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1286 SkScalar radius) {
1287 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001288
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 // need to know our prev pt so we can construct tangent vectors
1290 {
1291 SkPoint start;
1292 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001293 // Handle degenerate cases by adding a line to the first point and
1294 // bailing out.
1295 if ((x1 == start.fX && y1 == start.fY) ||
1296 (x1 == x2 && y1 == y2) ||
1297 radius == 0) {
1298 this->lineTo(x1, y1);
1299 return;
1300 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 before.setNormalize(x1 - start.fX, y1 - start.fY);
1302 after.setNormalize(x2 - x1, y2 - y1);
1303 }
reed@google.comabf15c12011-01-18 20:35:51 +00001304
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 SkScalar cosh = SkPoint::DotProduct(before, after);
1306 SkScalar sinh = SkPoint::CrossProduct(before, after);
1307
1308 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001309 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 return;
1311 }
reed@google.comabf15c12011-01-18 20:35:51 +00001312
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1314 if (dist < 0) {
1315 dist = -dist;
1316 }
1317
1318 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1319 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1320 SkRotationDirection arcDir;
1321
1322 // now turn before/after into normals
1323 if (sinh > 0) {
1324 before.rotateCCW();
1325 after.rotateCCW();
1326 arcDir = kCW_SkRotationDirection;
1327 } else {
1328 before.rotateCW();
1329 after.rotateCW();
1330 arcDir = kCCW_SkRotationDirection;
1331 }
1332
1333 SkMatrix matrix;
1334 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001335
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 matrix.setScale(radius, radius);
1337 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1338 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001339
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001341
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 this->incReserve(count);
1343 // [xx,yy] == pts[0]
1344 this->lineTo(xx, yy);
1345 for (int i = 1; i < count; i += 2) {
1346 this->quadTo(pts[i], pts[i+1]);
1347 }
1348}
1349
1350///////////////////////////////////////////////////////////////////////////////
1351
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001352void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001353 SkMatrix matrix;
1354
1355 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001356 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357}
1358
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001359void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001360 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001362 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 SkPoint pts[4];
1364 Verb verb;
1365
1366 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001367 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368 while ((verb = iter.next(pts)) != kDone_Verb) {
1369 switch (verb) {
1370 case kMove_Verb:
1371 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001372 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1373 injectMoveToIfNeeded(); // In case last contour is closed
1374 this->lineTo(pts[0]);
1375 } else {
1376 this->moveTo(pts[0]);
1377 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 break;
1379 case kLine_Verb:
1380 proc(matrix, &pts[1], &pts[1], 1);
1381 this->lineTo(pts[1]);
1382 break;
1383 case kQuad_Verb:
1384 proc(matrix, &pts[1], &pts[1], 2);
1385 this->quadTo(pts[1], pts[2]);
1386 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001387 case kConic_Verb:
1388 proc(matrix, &pts[1], &pts[1], 2);
1389 this->conicTo(pts[1], pts[2], iter.conicWeight());
1390 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 case kCubic_Verb:
1392 proc(matrix, &pts[1], &pts[1], 3);
1393 this->cubicTo(pts[1], pts[2], pts[3]);
1394 break;
1395 case kClose_Verb:
1396 this->close();
1397 break;
1398 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001399 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001401 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 }
1403}
1404
1405///////////////////////////////////////////////////////////////////////////////
1406
reed@google.com277c3f82013-05-31 15:17:50 +00001407static int pts_in_verb(unsigned verb) {
1408 static const uint8_t gPtsInVerb[] = {
1409 1, // kMove
1410 1, // kLine
1411 2, // kQuad
1412 2, // kConic
1413 3, // kCubic
1414 0, // kClose
1415 0 // kDone
1416 };
1417
1418 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1419 return gPtsInVerb[verb];
1420}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422// ignore the last point of the 1st contour
1423void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001424 int i, vcount = path.fPathRef->countVerbs();
1425 // exit early if the path is empty, or just has a moveTo.
1426 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 return;
1428 }
1429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001430 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001432 const uint8_t* verbs = path.fPathRef->verbs();
1433 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001434 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001436 SkASSERT(verbs[~0] == kMove_Verb);
1437 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001438 unsigned v = verbs[~i];
1439 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 if (n == 0) {
1441 break;
1442 }
1443 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001444 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 }
1446
1447 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001448 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 case kLine_Verb:
1450 this->lineTo(pts[-1].fX, pts[-1].fY);
1451 break;
1452 case kQuad_Verb:
1453 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1454 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001455 case kConic_Verb:
1456 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1457 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 case kCubic_Verb:
1459 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1460 pts[-3].fX, pts[-3].fY);
1461 break;
1462 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001463 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 break;
1465 }
reed@google.com277c3f82013-05-31 15:17:50 +00001466 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 }
1468}
1469
reed@google.com63d73742012-01-10 15:33:12 +00001470void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001471 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 const SkPoint* pts = src.fPathRef->pointsEnd();
1474 // we will iterator through src's verbs backwards
1475 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1476 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001477 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001478
1479 bool needMove = true;
1480 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001481 while (verbs < verbsEnd) {
1482 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001483 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001484
1485 if (needMove) {
1486 --pts;
1487 this->moveTo(pts->fX, pts->fY);
1488 needMove = false;
1489 }
1490 pts -= n;
1491 switch (v) {
1492 case kMove_Verb:
1493 if (needClose) {
1494 this->close();
1495 needClose = false;
1496 }
1497 needMove = true;
1498 pts += 1; // so we see the point in "if (needMove)" above
1499 break;
1500 case kLine_Verb:
1501 this->lineTo(pts[0]);
1502 break;
1503 case kQuad_Verb:
1504 this->quadTo(pts[1], pts[0]);
1505 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001506 case kConic_Verb:
1507 this->conicTo(pts[1], pts[0], *--conicWeights);
1508 break;
reed@google.com63d73742012-01-10 15:33:12 +00001509 case kCubic_Verb:
1510 this->cubicTo(pts[2], pts[1], pts[0]);
1511 break;
1512 case kClose_Verb:
1513 needClose = true;
1514 break;
1515 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001516 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001517 }
1518 }
1519}
1520
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521///////////////////////////////////////////////////////////////////////////////
1522
1523void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1524 SkMatrix matrix;
1525
1526 matrix.setTranslate(dx, dy);
1527 this->transform(matrix, dst);
1528}
1529
1530#include "SkGeometry.h"
1531
1532static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1533 int level = 2) {
1534 if (--level >= 0) {
1535 SkPoint tmp[5];
1536
1537 SkChopQuadAtHalf(pts, tmp);
1538 subdivide_quad_to(path, &tmp[0], level);
1539 subdivide_quad_to(path, &tmp[2], level);
1540 } else {
1541 path->quadTo(pts[1], pts[2]);
1542 }
1543}
1544
1545static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1546 int level = 2) {
1547 if (--level >= 0) {
1548 SkPoint tmp[7];
1549
1550 SkChopCubicAtHalf(pts, tmp);
1551 subdivide_cubic_to(path, &tmp[0], level);
1552 subdivide_cubic_to(path, &tmp[3], level);
1553 } else {
1554 path->cubicTo(pts[1], pts[2], pts[3]);
1555 }
1556}
1557
1558void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1559 SkDEBUGCODE(this->validate();)
1560 if (dst == NULL) {
1561 dst = (SkPath*)this;
1562 }
1563
tomhudson@google.com8d430182011-06-06 19:11:19 +00001564 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 SkPath tmp;
1566 tmp.fFillType = fFillType;
1567
1568 SkPath::Iter iter(*this, false);
1569 SkPoint pts[4];
1570 SkPath::Verb verb;
1571
reed@google.com4a3b7142012-05-16 17:16:46 +00001572 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 switch (verb) {
1574 case kMove_Verb:
1575 tmp.moveTo(pts[0]);
1576 break;
1577 case kLine_Verb:
1578 tmp.lineTo(pts[1]);
1579 break;
1580 case kQuad_Verb:
1581 subdivide_quad_to(&tmp, pts);
1582 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001583 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001584 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001585 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1586 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 case kCubic_Verb:
1588 subdivide_cubic_to(&tmp, pts);
1589 break;
1590 case kClose_Verb:
1591 tmp.close();
1592 break;
1593 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001594 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 break;
1596 }
1597 }
1598
1599 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001600 SkPathRef::Editor ed(&dst->fPathRef);
1601 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001602 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001604 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1605
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001608 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001610
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001611 if (kUnknown_Direction == fDirection) {
1612 dst->fDirection = kUnknown_Direction;
1613 } else {
1614 SkScalar det2x2 =
1615 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1616 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1617 if (det2x2 < 0) {
1618 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1619 } else if (det2x2 > 0) {
1620 dst->fDirection = fDirection;
1621 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001622 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001623 dst->fDirection = kUnknown_Direction;
1624 }
1625 }
1626
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 SkDEBUGCODE(dst->validate();)
1628 }
1629}
1630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631///////////////////////////////////////////////////////////////////////////////
1632///////////////////////////////////////////////////////////////////////////////
1633
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001634enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001635 kEmptyContour_SegmentState, // The current contour is empty. We may be
1636 // starting processing or we may have just
1637 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001638 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1639 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1640 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641};
1642
1643SkPath::Iter::Iter() {
1644#ifdef SK_DEBUG
1645 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001646 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001648 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001649 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650#endif
1651 // need to init enough to make next() harmlessly return kDone_Verb
1652 fVerbs = NULL;
1653 fVerbStop = NULL;
1654 fNeedClose = false;
1655}
1656
1657SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1658 this->setPath(path, forceClose);
1659}
1660
1661void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 fPts = path.fPathRef->points();
1663 fVerbs = path.fPathRef->verbs();
1664 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001665 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001666 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001667 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 fForceClose = SkToU8(forceClose);
1669 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001670 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671}
1672
1673bool SkPath::Iter::isClosedContour() const {
1674 if (fVerbs == NULL || fVerbs == fVerbStop) {
1675 return false;
1676 }
1677 if (fForceClose) {
1678 return true;
1679 }
1680
1681 const uint8_t* verbs = fVerbs;
1682 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001684 if (kMove_Verb == *(verbs - 1)) {
1685 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 }
1687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 while (verbs > stop) {
1689 // verbs points one beyond the current verb, decrement first.
1690 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 if (kMove_Verb == v) {
1692 break;
1693 }
1694 if (kClose_Verb == v) {
1695 return true;
1696 }
1697 }
1698 return false;
1699}
1700
1701SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001702 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001704 // A special case: if both points are NaN, SkPoint::operation== returns
1705 // false, but the iterator expects that they are treated as the same.
1706 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001707 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1708 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001709 return kClose_Verb;
1710 }
1711
reed@google.com9e25dbf2012-05-15 17:05:38 +00001712 pts[0] = fLastPt;
1713 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 fLastPt = fMoveTo;
1715 fCloseLine = true;
1716 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001717 } else {
1718 pts[0] = fMoveTo;
1719 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721}
1722
reed@google.com9e25dbf2012-05-15 17:05:38 +00001723const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 if (fSegmentState == kAfterMove_SegmentState) {
1725 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001727 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001729 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1730 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001731 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733}
1734
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735void SkPath::Iter::consumeDegenerateSegments() {
1736 // We need to step over anything that will not move the current draw point
1737 // forward before the next move is seen
1738 const uint8_t* lastMoveVerb = 0;
1739 const SkPoint* lastMovePt = 0;
1740 SkPoint lastPt = fLastPt;
1741 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 switch (verb) {
1744 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001745 // Keep a record of this most recent move
1746 lastMoveVerb = fVerbs;
1747 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001748 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001749 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001750 fPts++;
1751 break;
1752
1753 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001754 // A close when we are in a segment is always valid except when it
1755 // follows a move which follows a segment.
1756 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 return;
1758 }
1759 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761 break;
1762
1763 case kLine_Verb:
1764 if (!IsLineDegenerate(lastPt, fPts[0])) {
1765 if (lastMoveVerb) {
1766 fVerbs = lastMoveVerb;
1767 fPts = lastMovePt;
1768 return;
1769 }
1770 return;
1771 }
1772 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 fPts++;
1775 break;
1776
reed@google.com277c3f82013-05-31 15:17:50 +00001777 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001778 case kQuad_Verb:
1779 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1780 if (lastMoveVerb) {
1781 fVerbs = lastMoveVerb;
1782 fPts = lastMovePt;
1783 return;
1784 }
1785 return;
1786 }
1787 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001789 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001790 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001791 break;
1792
1793 case kCubic_Verb:
1794 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1795 if (lastMoveVerb) {
1796 fVerbs = lastMoveVerb;
1797 fPts = lastMovePt;
1798 return;
1799 }
1800 return;
1801 }
1802 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001803 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 fPts += 3;
1805 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001806
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001808 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 }
1810 }
1811}
1812
reed@google.com4a3b7142012-05-16 17:16:46 +00001813SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001814 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 // Close the curve if requested and if there is some curve to close
1818 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001819 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 return kLine_Verb;
1821 }
1822 fNeedClose = false;
1823 return kClose_Verb;
1824 }
1825 return kDone_Verb;
1826 }
1827
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001828 // fVerbs is one beyond the current verb, decrement first
1829 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001830 const SkPoint* SK_RESTRICT srcPts = fPts;
1831 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832
1833 switch (verb) {
1834 case kMove_Verb:
1835 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 verb = this->autoClose(pts);
1838 if (verb == kClose_Verb) {
1839 fNeedClose = false;
1840 }
1841 return (Verb)verb;
1842 }
1843 if (fVerbs == fVerbStop) { // might be a trailing moveto
1844 return kDone_Verb;
1845 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001846 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001847 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001849 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 fNeedClose = fForceClose;
1852 break;
1853 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001854 pts[0] = this->cons_moveTo();
1855 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 fLastPt = srcPts[0];
1857 fCloseLine = false;
1858 srcPts += 1;
1859 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001860 case kConic_Verb:
1861 fConicWeights += 1;
1862 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001864 pts[0] = this->cons_moveTo();
1865 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 fLastPt = srcPts[1];
1867 srcPts += 2;
1868 break;
1869 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001870 pts[0] = this->cons_moveTo();
1871 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872 fLastPt = srcPts[2];
1873 srcPts += 3;
1874 break;
1875 case kClose_Verb:
1876 verb = this->autoClose(pts);
1877 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001878 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 } else {
1880 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001881 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 break;
1885 }
1886 fPts = srcPts;
1887 return (Verb)verb;
1888}
1889
1890///////////////////////////////////////////////////////////////////////////////
1891
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001892SkPath::RawIter::RawIter() {
1893#ifdef SK_DEBUG
1894 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001895 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001896 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1897#endif
1898 // need to init enough to make next() harmlessly return kDone_Verb
1899 fVerbs = NULL;
1900 fVerbStop = NULL;
1901}
1902
1903SkPath::RawIter::RawIter(const SkPath& path) {
1904 this->setPath(path);
1905}
1906
1907void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001908 fPts = path.fPathRef->points();
1909 fVerbs = path.fPathRef->verbs();
1910 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001911 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001912 fMoveTo.fX = fMoveTo.fY = 0;
1913 fLastPt.fX = fLastPt.fY = 0;
1914}
1915
1916SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001917 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001918 if (fVerbs == fVerbStop) {
1919 return kDone_Verb;
1920 }
1921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 // fVerbs points one beyond next verb so decrement first.
1923 unsigned verb = *(--fVerbs);
1924 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001925
1926 switch (verb) {
1927 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001928 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001929 fMoveTo = srcPts[0];
1930 fLastPt = fMoveTo;
1931 srcPts += 1;
1932 break;
1933 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001934 pts[0] = fLastPt;
1935 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001936 fLastPt = srcPts[0];
1937 srcPts += 1;
1938 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001939 case kConic_Verb:
1940 fConicWeights += 1;
1941 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001942 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001943 pts[0] = fLastPt;
1944 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001945 fLastPt = srcPts[1];
1946 srcPts += 2;
1947 break;
1948 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001949 pts[0] = fLastPt;
1950 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001951 fLastPt = srcPts[2];
1952 srcPts += 3;
1953 break;
1954 case kClose_Verb:
1955 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001956 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001957 break;
1958 }
1959 fPts = srcPts;
1960 return (Verb)verb;
1961}
1962
1963///////////////////////////////////////////////////////////////////////////////
1964
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001966 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967*/
1968
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001969size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 SkDEBUGCODE(this->validate();)
1971
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001972 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001973 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001974 return SkAlign4(byteCount);
1975 }
1976
1977 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001978
robertphillips@google.com466310d2013-12-03 16:43:54 +00001979 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001980 (fFillType << kFillType_SerializationShift) |
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001981 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001982
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001983 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001984
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001985 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001986
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001987 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001988 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989}
1990
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001991size_t SkPath::readFromMemory(const void* storage, size_t length) {
1992 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001993
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001994 int32_t packed;
1995 if (!buffer.readS32(&packed)) {
1996 return 0;
1997 }
1998
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001999 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2000 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002001 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002002 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002003
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002004 size_t sizeRead = 0;
2005 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002006 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002007 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002008 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002009 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002010 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002011 // If the buffer is not valid, pathRef should be NULL
2012 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002013 }
2014 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015}
2016
2017///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018
reed@google.com51bbe752013-01-17 22:07:50 +00002019#include "SkString.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002020#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002021
caryclarke9562592014-09-15 09:26:09 -07002022static void append_scalar(SkString* str, SkScalar value, bool dumpAsHex) {
2023 if (dumpAsHex) {
2024 str->appendf("SkBits2Float(0x%08x)", SkFloat2Bits(value));
2025 return;
2026 }
reed@google.com51bbe752013-01-17 22:07:50 +00002027 SkString tmp;
2028 tmp.printf("%g", value);
2029 if (tmp.contains('.')) {
2030 tmp.appendUnichar('f');
2031 }
2032 str->append(tmp);
2033}
2034
2035static void append_params(SkString* str, const char label[], const SkPoint pts[],
caryclarke9562592014-09-15 09:26:09 -07002036 int count, bool dumpAsHex, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002037 str->append(label);
2038 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002039
reed@google.com51bbe752013-01-17 22:07:50 +00002040 const SkScalar* values = &pts[0].fX;
2041 count *= 2;
2042
2043 for (int i = 0; i < count; ++i) {
caryclarke9562592014-09-15 09:26:09 -07002044 append_scalar(str, values[i], dumpAsHex);
reed@google.com51bbe752013-01-17 22:07:50 +00002045 if (i < count - 1) {
2046 str->append(", ");
2047 }
2048 }
reed@google.com277c3f82013-05-31 15:17:50 +00002049 if (conicWeight >= 0) {
2050 str->append(", ");
caryclarke9562592014-09-15 09:26:09 -07002051 append_scalar(str, conicWeight, dumpAsHex);
reed@google.com277c3f82013-05-31 15:17:50 +00002052 }
reed@google.com51bbe752013-01-17 22:07:50 +00002053 str->append(");\n");
2054}
2055
caryclarke9562592014-09-15 09:26:09 -07002056void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 Iter iter(*this, forceClose);
2058 SkPoint pts[4];
2059 Verb verb;
2060
caryclark66a5d8b2014-06-24 08:30:15 -07002061 if (!wStream) {
2062 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2063 }
reed@google.com51bbe752013-01-17 22:07:50 +00002064 SkString builder;
2065
reed@google.com4a3b7142012-05-16 17:16:46 +00002066 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 switch (verb) {
2068 case kMove_Verb:
caryclarke9562592014-09-15 09:26:09 -07002069 append_params(&builder, "path.moveTo", &pts[0], 1, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 break;
2071 case kLine_Verb:
caryclarke9562592014-09-15 09:26:09 -07002072 append_params(&builder, "path.lineTo", &pts[1], 1, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073 break;
2074 case kQuad_Verb:
caryclarke9562592014-09-15 09:26:09 -07002075 append_params(&builder, "path.quadTo", &pts[1], 2, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002077 case kConic_Verb:
caryclarke9562592014-09-15 09:26:09 -07002078 append_params(&builder, "path.conicTo", &pts[1], 2, dumpAsHex, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002079 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 case kCubic_Verb:
caryclarke9562592014-09-15 09:26:09 -07002081 append_params(&builder, "path.cubicTo", &pts[1], 3, dumpAsHex);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 break;
2083 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002084 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085 break;
2086 default:
2087 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2088 verb = kDone_Verb; // stop the loop
2089 break;
2090 }
2091 }
caryclark66a5d8b2014-06-24 08:30:15 -07002092 if (wStream) {
2093 wStream->writeText(builder.c_str());
2094 } else {
2095 SkDebugf("%s", builder.c_str());
2096 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097}
2098
reed@android.come522ca52009-11-23 20:10:41 +00002099void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002100 this->dump(NULL, false, false);
2101}
2102
2103void SkPath::dumpHex() const {
2104 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002105}
2106
2107#ifdef SK_DEBUG
2108void SkPath::validate() const {
2109 SkASSERT(this != NULL);
2110 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002111
djsollen@google.com077348c2012-10-22 20:23:32 +00002112#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002113 if (!fBoundsIsDirty) {
2114 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002115
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002116 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002117 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002118
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002119 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002120 // if we're empty, fBounds may be empty but translated, so we can't
2121 // necessarily compare to bounds directly
2122 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2123 // be [2, 2, 2, 2]
2124 SkASSERT(bounds.isEmpty());
2125 SkASSERT(fBounds.isEmpty());
2126 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002127 if (bounds.isEmpty()) {
2128 SkASSERT(fBounds.isEmpty());
2129 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002130 if (!fBounds.isEmpty()) {
2131 SkASSERT(fBounds.contains(bounds));
2132 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002133 }
reed@android.come522ca52009-11-23 20:10:41 +00002134 }
2135 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002136#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002137}
djsollen@google.com077348c2012-10-22 20:23:32 +00002138#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002139
reed@google.com04863fa2011-05-15 04:08:24 +00002140///////////////////////////////////////////////////////////////////////////////
2141
reed@google.com0b7b9822011-05-16 12:29:27 +00002142static int sign(SkScalar x) { return x < 0; }
2143#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002144
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002145static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002146 // The error epsilon was empirically derived; worse case round rects
2147 // with a mid point outset by 2x float epsilon in tests had an error
2148 // of 12.
2149 const int epsilon = 16;
2150 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2151 return false;
2152 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002153 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002154 int aBits = SkFloatAs2sCompliment(compA);
2155 int bBits = SkFloatAs2sCompliment(compB);
2156 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002157}
2158
2159// only valid for a single contour
2160struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002161 Convexicator()
2162 : fPtCount(0)
2163 , fConvexity(SkPath::kConvex_Convexity)
2164 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002165 fSign = 0;
2166 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002167 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002168 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002169 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002170 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002171
2172 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002173 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002174 }
2175
2176 SkPath::Convexity getConvexity() const { return fConvexity; }
2177
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002178 /** The direction returned is only valid if the path is determined convex */
2179 SkPath::Direction getDirection() const { return fDirection; }
2180
reed@google.com04863fa2011-05-15 04:08:24 +00002181 void addPt(const SkPoint& pt) {
2182 if (SkPath::kConcave_Convexity == fConvexity) {
2183 return;
2184 }
2185
2186 if (0 == fPtCount) {
2187 fCurrPt = pt;
2188 ++fPtCount;
2189 } else {
2190 SkVector vec = pt - fCurrPt;
2191 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002192 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002193 fCurrPt = pt;
2194 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002195 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002196 } else {
2197 SkASSERT(fPtCount > 2);
2198 this->addVec(vec);
2199 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002200
reed@google.com85b6e392011-05-15 20:25:17 +00002201 int sx = sign(vec.fX);
2202 int sy = sign(vec.fY);
2203 fDx += (sx != fSx);
2204 fDy += (sy != fSy);
2205 fSx = sx;
2206 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002207
reed@google.com85b6e392011-05-15 20:25:17 +00002208 if (fDx > 3 || fDy > 3) {
2209 fConvexity = SkPath::kConcave_Convexity;
2210 }
reed@google.com04863fa2011-05-15 04:08:24 +00002211 }
2212 }
2213 }
2214
2215 void close() {
2216 if (fPtCount > 2) {
2217 this->addVec(fFirstVec);
2218 }
2219 }
2220
2221private:
2222 void addVec(const SkVector& vec) {
2223 SkASSERT(vec.fX || vec.fY);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002224 SkScalar cross = SkPoint::CrossProduct(fLastVec, vec);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002225 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2226 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2227 largest = SkTMax(largest, -smallest);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002228 if (!almost_equal(largest, largest + cross)) {
2229 int sign = SkScalarSignAsInt(cross);
2230 if (0 == fSign) {
2231 fSign = sign;
2232 fDirection = (1 == sign) ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2233 } else if (sign && fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002234 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002235 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 }
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002237 fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002238 }
2239 }
2240
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002241 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002242 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002243 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2244 // value with the current vec is deemed to be of a significant value.
2245 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002246 int fPtCount; // non-degenerate points
2247 int fSign;
2248 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002249 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002250 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002251};
2252
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002253SkPath::Convexity SkPath::internalGetConvexity() const {
2254 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002255 SkPoint pts[4];
2256 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002257 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002258
2259 int contourCount = 0;
2260 int count;
2261 Convexicator state;
2262
2263 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2264 switch (verb) {
2265 case kMove_Verb:
2266 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002267 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 return kConcave_Convexity;
2269 }
2270 pts[1] = pts[0];
2271 count = 1;
2272 break;
2273 case kLine_Verb: count = 1; break;
2274 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002275 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002276 case kCubic_Verb: count = 3; break;
2277 case kClose_Verb:
2278 state.close();
2279 count = 0;
2280 break;
2281 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002282 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002283 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 return kConcave_Convexity;
2285 }
2286
2287 for (int i = 1; i <= count; i++) {
2288 state.addPt(pts[i]);
2289 }
2290 // early exit
2291 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002292 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002293 return kConcave_Convexity;
2294 }
2295 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002296 fConvexity = state.getConvexity();
2297 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2298 fDirection = state.getDirection();
2299 }
2300 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002301}
reed@google.com69a99432012-01-10 18:00:10 +00002302
2303///////////////////////////////////////////////////////////////////////////////
2304
2305class ContourIter {
2306public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002307 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002308
2309 bool done() const { return fDone; }
2310 // if !done() then these may be called
2311 int count() const { return fCurrPtCount; }
2312 const SkPoint* pts() const { return fCurrPt; }
2313 void next();
2314
2315private:
2316 int fCurrPtCount;
2317 const SkPoint* fCurrPt;
2318 const uint8_t* fCurrVerb;
2319 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002320 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002321 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002322 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002323};
2324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002325ContourIter::ContourIter(const SkPathRef& pathRef) {
2326 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002327 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002328 fCurrPt = pathRef.points();
2329 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002330 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002331 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002332 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002333 this->next();
2334}
2335
2336void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002337 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002338 fDone = true;
2339 }
2340 if (fDone) {
2341 return;
2342 }
2343
2344 // skip pts of prev contour
2345 fCurrPt += fCurrPtCount;
2346
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002347 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002348 int ptCount = 1; // moveTo
2349 const uint8_t* verbs = fCurrVerb;
2350
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002351 for (--verbs; verbs > fStopVerbs; --verbs) {
2352 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002353 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002354 goto CONTOUR_END;
2355 case SkPath::kLine_Verb:
2356 ptCount += 1;
2357 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002358 case SkPath::kConic_Verb:
2359 fCurrConicWeight += 1;
2360 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002361 case SkPath::kQuad_Verb:
2362 ptCount += 2;
2363 break;
2364 case SkPath::kCubic_Verb:
2365 ptCount += 3;
2366 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002367 case SkPath::kClose_Verb:
2368 break;
2369 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002370 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002371 break;
2372 }
2373 }
2374CONTOUR_END:
2375 fCurrPtCount = ptCount;
2376 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002377 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002378}
2379
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002380// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002381static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002382 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2383 // We may get 0 when the above subtracts underflow. We expect this to be
2384 // very rare and lazily promote to double.
2385 if (0 == cross) {
2386 double p0x = SkScalarToDouble(p0.fX);
2387 double p0y = SkScalarToDouble(p0.fY);
2388
2389 double p1x = SkScalarToDouble(p1.fX);
2390 double p1y = SkScalarToDouble(p1.fY);
2391
2392 double p2x = SkScalarToDouble(p2.fX);
2393 double p2y = SkScalarToDouble(p2.fY);
2394
2395 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2396 (p1y - p0y) * (p2x - p0x));
2397
2398 }
2399 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002400}
2401
reed@google.comc1ea60a2012-01-31 15:15:36 +00002402// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002403static int find_max_y(const SkPoint pts[], int count) {
2404 SkASSERT(count > 0);
2405 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002406 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002407 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002408 SkScalar y = pts[i].fY;
2409 if (y > max) {
2410 max = y;
2411 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002412 }
2413 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002414 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002415}
2416
reed@google.comcabaf1d2012-01-11 21:03:05 +00002417static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2418 int i = index;
2419 for (;;) {
2420 i = (i + inc) % n;
2421 if (i == index) { // we wrapped around, so abort
2422 break;
2423 }
2424 if (pts[index] != pts[i]) { // found a different point, success!
2425 break;
2426 }
2427 }
2428 return i;
2429}
2430
reed@google.comc1ea60a2012-01-31 15:15:36 +00002431/**
2432 * Starting at index, and moving forward (incrementing), find the xmin and
2433 * xmax of the contiguous points that have the same Y.
2434 */
2435static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2436 int* maxIndexPtr) {
2437 const SkScalar y = pts[index].fY;
2438 SkScalar min = pts[index].fX;
2439 SkScalar max = min;
2440 int minIndex = index;
2441 int maxIndex = index;
2442 for (int i = index + 1; i < n; ++i) {
2443 if (pts[i].fY != y) {
2444 break;
2445 }
2446 SkScalar x = pts[i].fX;
2447 if (x < min) {
2448 min = x;
2449 minIndex = i;
2450 } else if (x > max) {
2451 max = x;
2452 maxIndex = i;
2453 }
2454 }
2455 *maxIndexPtr = maxIndex;
2456 return minIndex;
2457}
2458
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002459static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002460 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002461}
2462
reed@google.comac8543f2012-01-30 20:51:25 +00002463/*
2464 * We loop through all contours, and keep the computed cross-product of the
2465 * contour that contained the global y-max. If we just look at the first
2466 * contour, we may find one that is wound the opposite way (correctly) since
2467 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2468 * that is outer most (or at least has the global y-max) before we can consider
2469 * its cross product.
2470 */
reed@google.com69a99432012-01-10 18:00:10 +00002471bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472 if (kUnknown_Direction != fDirection) {
2473 *dir = static_cast<Direction>(fDirection);
2474 return true;
2475 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002476
2477 // don't want to pay the cost for computing this if it
2478 // is unknown, so we don't call isConvex()
2479 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2480 SkASSERT(kUnknown_Direction == fDirection);
2481 *dir = static_cast<Direction>(fDirection);
2482 return false;
2483 }
reed@google.com69a99432012-01-10 18:00:10 +00002484
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002485 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002486
reed@google.comac8543f2012-01-30 20:51:25 +00002487 // initialize with our logical y-min
2488 SkScalar ymax = this->getBounds().fTop;
2489 SkScalar ymaxCross = 0;
2490
reed@google.com69a99432012-01-10 18:00:10 +00002491 for (; !iter.done(); iter.next()) {
2492 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002493 if (n < 3) {
2494 continue;
2495 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002496
reed@google.comcabaf1d2012-01-11 21:03:05 +00002497 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002498 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002499 int index = find_max_y(pts, n);
2500 if (pts[index].fY < ymax) {
2501 continue;
2502 }
2503
2504 // If there is more than 1 distinct point at the y-max, we take the
2505 // x-min and x-max of them and just subtract to compute the dir.
2506 if (pts[(index + 1) % n].fY == pts[index].fY) {
2507 int maxIndex;
2508 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2509 if (minIndex == maxIndex) {
2510 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002511 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002512 SkASSERT(pts[minIndex].fY == pts[index].fY);
2513 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2514 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2515 // we just subtract the indices, and let that auto-convert to
2516 // SkScalar, since we just want - or + to signal the direction.
2517 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002518 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002519 TRY_CROSSPROD:
2520 // Find a next and prev index to use for the cross-product test,
2521 // but we try to find pts that form non-zero vectors from pts[index]
2522 //
2523 // Its possible that we can't find two non-degenerate vectors, so
2524 // we have to guard our search (e.g. all the pts could be in the
2525 // same place).
2526
2527 // we pass n - 1 instead of -1 so we don't foul up % operator by
2528 // passing it a negative LH argument.
2529 int prev = find_diff_pt(pts, index, n, n - 1);
2530 if (prev == index) {
2531 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002532 continue;
2533 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002534 int next = find_diff_pt(pts, index, n, 1);
2535 SkASSERT(next != index);
2536 cross = cross_prod(pts[prev], pts[index], pts[next]);
2537 // if we get a zero and the points are horizontal, then we look at the spread in
2538 // x-direction. We really should continue to walk away from the degeneracy until
2539 // there is a divergence.
2540 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2541 // construct the subtract so we get the correct Direction below
2542 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002543 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002544 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002545
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002546 if (cross) {
2547 // record our best guess so far
2548 ymax = pts[index].fY;
2549 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002550 }
2551 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002552 if (ymaxCross) {
2553 crossToDir(ymaxCross, dir);
2554 fDirection = *dir;
2555 return true;
2556 } else {
2557 return false;
2558 }
reed@google.comac8543f2012-01-30 20:51:25 +00002559}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002560
2561///////////////////////////////////////////////////////////////////////////////
2562
2563static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2564 SkScalar D, SkScalar t) {
2565 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2566}
2567
2568static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2569 SkScalar t) {
2570 SkScalar A = c3 + 3*(c1 - c2) - c0;
2571 SkScalar B = 3*(c2 - c1 - c1 + c0);
2572 SkScalar C = 3*(c1 - c0);
2573 SkScalar D = c0;
2574 return eval_cubic_coeff(A, B, C, D, t);
2575}
2576
2577/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2578 t value such that cubic(t) = target
2579 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002580static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002581 SkScalar target, SkScalar* t) {
2582 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2583 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002584
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002585 SkScalar D = c0 - target;
2586 SkScalar A = c3 + 3*(c1 - c2) - c0;
2587 SkScalar B = 3*(c2 - c1 - c1 + c0);
2588 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002589
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002590 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2591 SkScalar minT = 0;
2592 SkScalar maxT = SK_Scalar1;
2593 SkScalar mid;
2594 int i;
2595 for (i = 0; i < 16; i++) {
2596 mid = SkScalarAve(minT, maxT);
2597 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2598 if (delta < 0) {
2599 minT = mid;
2600 delta = -delta;
2601 } else {
2602 maxT = mid;
2603 }
2604 if (delta < TOLERANCE) {
2605 break;
2606 }
2607 }
2608 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002609}
2610
2611template <size_t N> static void find_minmax(const SkPoint pts[],
2612 SkScalar* minPtr, SkScalar* maxPtr) {
2613 SkScalar min, max;
2614 min = max = pts[0].fX;
2615 for (size_t i = 1; i < N; ++i) {
2616 min = SkMinScalar(min, pts[i].fX);
2617 max = SkMaxScalar(max, pts[i].fX);
2618 }
2619 *minPtr = min;
2620 *maxPtr = max;
2621}
2622
2623static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2624 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002625
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002626 int dir = 1;
2627 if (pts[0].fY > pts[3].fY) {
2628 storage[0] = pts[3];
2629 storage[1] = pts[2];
2630 storage[2] = pts[1];
2631 storage[3] = pts[0];
2632 pts = storage;
2633 dir = -1;
2634 }
2635 if (y < pts[0].fY || y >= pts[3].fY) {
2636 return 0;
2637 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002638
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002639 // quickreject or quickaccept
2640 SkScalar min, max;
2641 find_minmax<4>(pts, &min, &max);
2642 if (x < min) {
2643 return 0;
2644 }
2645 if (x > max) {
2646 return dir;
2647 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002648
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002649 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002650 SkScalar t;
2651 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2652 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 +00002653 return xt < x ? dir : 0;
2654}
2655
2656static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2657 SkPoint dst[10];
2658 int n = SkChopCubicAtYExtrema(pts, dst);
2659 int w = 0;
2660 for (int i = 0; i <= n; ++i) {
2661 w += winding_mono_cubic(&dst[i * 3], x, y);
2662 }
2663 return w;
2664}
2665
2666static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2667 SkScalar y0 = pts[0].fY;
2668 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002669
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002670 int dir = 1;
2671 if (y0 > y2) {
2672 SkTSwap(y0, y2);
2673 dir = -1;
2674 }
2675 if (y < y0 || y >= y2) {
2676 return 0;
2677 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002678
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002679 // bounds check on X (not required. is it faster?)
2680#if 0
2681 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2682 return 0;
2683 }
2684#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002685
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002686 SkScalar roots[2];
2687 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2688 2 * (pts[1].fY - pts[0].fY),
2689 pts[0].fY - y,
2690 roots);
2691 SkASSERT(n <= 1);
2692 SkScalar xt;
2693 if (0 == n) {
2694 SkScalar mid = SkScalarAve(y0, y2);
2695 // Need [0] and [2] if dir == 1
2696 // and [2] and [0] if dir == -1
2697 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2698 } else {
2699 SkScalar t = roots[0];
2700 SkScalar C = pts[0].fX;
2701 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2702 SkScalar B = 2 * (pts[1].fX - C);
2703 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2704 }
2705 return xt < x ? dir : 0;
2706}
2707
2708static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2709 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2710 if (y0 == y1) {
2711 return true;
2712 }
2713 if (y0 < y1) {
2714 return y1 <= y2;
2715 } else {
2716 return y1 >= y2;
2717 }
2718}
2719
2720static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2721 SkPoint dst[5];
2722 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2725 n = SkChopQuadAtYExtrema(pts, dst);
2726 pts = dst;
2727 }
2728 int w = winding_mono_quad(pts, x, y);
2729 if (n > 0) {
2730 w += winding_mono_quad(&pts[2], x, y);
2731 }
2732 return w;
2733}
2734
2735static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2736 SkScalar x0 = pts[0].fX;
2737 SkScalar y0 = pts[0].fY;
2738 SkScalar x1 = pts[1].fX;
2739 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002740
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002741 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002742
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002743 int dir = 1;
2744 if (y0 > y1) {
2745 SkTSwap(y0, y1);
2746 dir = -1;
2747 }
2748 if (y < y0 || y >= y1) {
2749 return 0;
2750 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002751
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2753 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002754
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755 if (SkScalarSignAsInt(cross) == dir) {
2756 dir = 0;
2757 }
2758 return dir;
2759}
2760
reed@google.com4db592c2013-10-30 17:39:43 +00002761static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2762 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2763}
2764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765bool SkPath::contains(SkScalar x, SkScalar y) const {
2766 bool isInverse = this->isInverseFillType();
2767 if (this->isEmpty()) {
2768 return isInverse;
2769 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002770
reed@google.com4db592c2013-10-30 17:39:43 +00002771 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772 return isInverse;
2773 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775 SkPath::Iter iter(*this, true);
2776 bool done = false;
2777 int w = 0;
2778 do {
2779 SkPoint pts[4];
2780 switch (iter.next(pts, false)) {
2781 case SkPath::kMove_Verb:
2782 case SkPath::kClose_Verb:
2783 break;
2784 case SkPath::kLine_Verb:
2785 w += winding_line(pts, x, y);
2786 break;
2787 case SkPath::kQuad_Verb:
2788 w += winding_quad(pts, x, y);
2789 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002790 case SkPath::kConic_Verb:
2791 SkASSERT(0);
2792 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 case SkPath::kCubic_Verb:
2794 w += winding_cubic(pts, x, y);
2795 break;
2796 case SkPath::kDone_Verb:
2797 done = true;
2798 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002799 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002801
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002802 switch (this->getFillType()) {
2803 case SkPath::kEvenOdd_FillType:
2804 case SkPath::kInverseEvenOdd_FillType:
2805 w &= 1;
2806 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002807 default:
2808 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 }
2810 return SkToBool(w);
2811}