blob: 6e09af78dd1a03a5ac80f5aa0d87dfc379f19465 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000012#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
reed220f9262014-12-17 08:21:04 -080017#define SK_SUPPORT_LEGACY_ADDOVAL
18
reed@android.com8a1c16f2008-12-17 15:59:43 +000019////////////////////////////////////////////////////////////////////////////
20
reed@google.com3563c9e2011-11-14 19:34:57 +000021/**
22 * Path.bounds is defined to be the bounds of all the control points.
23 * If we called bounds.join(r) we would skip r if r was empty, which breaks
24 * our promise. Hence we have a custom joiner that doesn't look at emptiness
25 */
26static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
27 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
28 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
29 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
30 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
31}
32
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000033static bool is_degenerate(const SkPath& path) {
34 SkPath::Iter iter(path, false);
35 SkPoint pts[4];
36 return SkPath::kDone_Verb == iter.next(pts);
37}
38
bsalomon@google.com30c174b2012-11-13 14:36:42 +000039class SkAutoDisableDirectionCheck {
40public:
41 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
42 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
43 }
44
45 ~SkAutoDisableDirectionCheck() {
46 fPath->fDirection = fSaved;
47 }
48
49private:
50 SkPath* fPath;
51 SkPath::Direction fSaved;
52};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000053#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000054
reed@android.com8a1c16f2008-12-17 15:59:43 +000055/* This guy's constructor/destructor bracket a path editing operation. It is
56 used when we know the bounds of the amount we are going to add to the path
57 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000060 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000061 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000062
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000063 It also notes if the path was originally degenerate, and if so, sets
64 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000065 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 */
67class SkAutoPathBoundsUpdate {
68public:
69 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
70 this->init(path);
71 }
72
73 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
74 SkScalar right, SkScalar bottom) {
75 fRect.set(left, top, right, bottom);
76 this->init(path);
77 }
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000080 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
81 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000082 if (fEmpty || fHasValidBounds) {
83 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 }
85 }
reed@google.comabf15c12011-01-18 20:35:51 +000086
reed@android.com8a1c16f2008-12-17 15:59:43 +000087private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000088 SkPath* fPath;
89 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000090 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000091 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000093
reed@android.com6b82d1a2009-06-03 02:35:01 +000094 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000095 // Cannot use fRect for our bounds unless we know it is sorted
96 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000098 // Mark the path's bounds as dirty if (1) they are, or (2) the path
99 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 if (fHasValidBounds && !fEmpty) {
103 joinNoEmptyChecks(&fRect, fPath->getBounds());
104 }
105 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 }
107};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000108#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110////////////////////////////////////////////////////////////////////////////
111
112/*
113 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000114 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
116
117 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000118 1. If we encounter degenerate segments, remove them
119 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
120 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
121 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122*/
123
124////////////////////////////////////////////////////////////////////////////
125
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000126// flag to require a moveTo if we begin with something else, like lineTo etc.
127#define INITIAL_LASTMOVETOINDEX_VALUE ~0
128
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000129SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000130 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000132 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000133#endif
134{
135 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700136 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137}
138
139void SkPath::resetFields() {
140 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000141 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000142 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000143 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000144 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000145
146 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
147 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000148}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000152 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000154 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000155#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157}
158
159SkPath::~SkPath() {
160 SkDEBUGCODE(this->validate();)
161}
162
bungeman@google.coma5809a32013-06-21 15:13:34 +0000163SkPath& SkPath::operator=(const SkPath& that) {
164 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166 if (this != &that) {
167 fPathRef.reset(SkRef(that.fPathRef.get()));
168 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000170 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000171#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 }
173 SkDEBUGCODE(this->validate();)
174 return *this;
175}
176
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177void SkPath::copyFields(const SkPath& that) {
178 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000179 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000180 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fConvexity = that.fConvexity;
182 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700183 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184}
185
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000186bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000187 // note: don't need to look at isConvex or bounds, since just comparing the
188 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000189 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000190 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000191}
192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193void SkPath::swap(SkPath& that) {
194 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 if (this != &that) {
197 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000198 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000200 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
201 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000203#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000204 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
205#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206 }
207}
208
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000209static inline bool check_edge_against_rect(const SkPoint& p0,
210 const SkPoint& p1,
211 const SkRect& rect,
212 SkPath::Direction dir) {
213 const SkPoint* edgeBegin;
214 SkVector v;
215 if (SkPath::kCW_Direction == dir) {
216 v = p1 - p0;
217 edgeBegin = &p0;
218 } else {
219 v = p0 - p1;
220 edgeBegin = &p1;
221 }
222 if (v.fX || v.fY) {
223 // check the cross product of v with the vec from edgeBegin to each rect corner
224 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
225 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
226 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
227 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
228 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
229 return false;
230 }
231 }
232 return true;
233}
234
235bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
236 // This only handles non-degenerate convex paths currently.
237 if (kConvex_Convexity != this->getConvexity()) {
238 return false;
239 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000240
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241 Direction direction;
242 if (!this->cheapComputeDirection(&direction)) {
243 return false;
244 }
245
246 SkPoint firstPt;
247 SkPoint prevPt;
248 RawIter iter(*this);
249 SkPath::Verb verb;
250 SkPoint pts[4];
251 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkDEBUGCODE(int segmentCount = 0;)
253 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254
255 while ((verb = iter.next(pts)) != kDone_Verb) {
256 int nextPt = -1;
257 switch (verb) {
258 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000259 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 SkDEBUGCODE(++moveCnt);
261 firstPt = prevPt = pts[0];
262 break;
263 case kLine_Verb:
264 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000265 SkASSERT(moveCnt && !closeCount);
266 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000267 break;
268 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000269 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000270 SkASSERT(moveCnt && !closeCount);
271 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 nextPt = 2;
273 break;
274 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000275 SkASSERT(moveCnt && !closeCount);
276 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 nextPt = 3;
278 break;
279 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281 break;
282 default:
283 SkDEBUGFAIL("unknown verb");
284 }
285 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800286 if (SkPath::kConic_Verb == verb) {
287 SkConic orig;
288 orig.set(pts, iter.conicWeight());
289 SkPoint quadPts[5];
290 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
291 SK_ALWAYSBREAK(2 == count);
292
293 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
294 return false;
295 }
296 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
297 return false;
298 }
299 } else {
300 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
301 return false;
302 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000303 }
304 prevPt = pts[nextPt];
305 }
306 }
307
308 return check_edge_against_rect(prevPt, firstPt, rect, direction);
309}
310
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000311uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000312 uint32_t genID = fPathRef->genID();
313#ifdef SK_BUILD_FOR_ANDROID
314 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
315 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
316#endif
317 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000318}
djsollen@google.come63793a2012-03-21 15:39:03 +0000319
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000320#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000321const SkPath* SkPath::getSourcePath() const {
322 return fSourcePath;
323}
324
325void SkPath::setSourcePath(const SkPath* path) {
326 fSourcePath = path;
327}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000328#endif
329
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330void SkPath::reset() {
331 SkDEBUGCODE(this->validate();)
332
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000334 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335}
336
337void SkPath::rewind() {
338 SkDEBUGCODE(this->validate();)
339
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000340 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000341 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342}
343
reed@google.com7e6c4d12012-05-10 14:05:43 +0000344bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000345 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000347 if (2 == verbCount) {
348 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
349 if (kLine_Verb == fPathRef->atVerb(1)) {
350 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000351 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000352 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000353 line[0] = pts[0];
354 line[1] = pts[1];
355 }
356 return true;
357 }
358 }
359 return false;
360}
361
caryclark@google.comf1316942011-07-26 19:54:45 +0000362/*
363 Determines if path is a rect by keeping track of changes in direction
364 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000365
caryclark@google.comf1316942011-07-26 19:54:45 +0000366 The direction is computed such that:
367 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000368 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000369 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000370 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000371
caryclark@google.comf1316942011-07-26 19:54:45 +0000372A rectangle cycles up/right/down/left or up/left/down/right.
373
374The test fails if:
375 The path is closed, and followed by a line.
376 A second move creates a new endpoint.
377 A diagonal line is parsed.
378 There's more than four changes of direction.
379 There's a discontinuity on the line (e.g., a move in the middle)
380 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000381 The path contains a quadratic or cubic.
382 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000383 *The rectangle doesn't complete a cycle.
384 *The final point isn't equal to the first point.
385
386 *These last two conditions we relax if we have a 3-edge path that would
387 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000388
caryclark@google.comf1316942011-07-26 19:54:45 +0000389It's OK if the path has:
390 Several colinear line segments composing a rectangle side.
391 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000392
caryclark@google.comf1316942011-07-26 19:54:45 +0000393The direction takes advantage of the corners found since opposite sides
394must travel in opposite directions.
395
396FIXME: Allow colinear quads and cubics to be treated like lines.
397FIXME: If the API passes fill-only, return true if the filled stroke
398 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000399
400 first,last,next direction state-machine:
401 0x1 is set if the segment is horizontal
402 0x2 is set if the segment is moving to the right or down
403 thus:
404 two directions are opposites iff (dirA ^ dirB) == 0x2
405 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000406
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000408static int rect_make_dir(SkScalar dx, SkScalar dy) {
409 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
410}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000411bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
412 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000413 int corners = 0;
414 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000415 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000416 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000417 first.set(0, 0);
418 last.set(0, 0);
419 int firstDirection = 0;
420 int lastDirection = 0;
421 int nextDirection = 0;
422 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000423 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000424 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000425 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
426 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000427 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000428 savePts = pts;
429 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000430 autoClose = true;
431 case kLine_Verb: {
432 SkScalar left = last.fX;
433 SkScalar top = last.fY;
434 SkScalar right = pts->fX;
435 SkScalar bottom = pts->fY;
436 ++pts;
437 if (left != right && top != bottom) {
438 return false; // diagonal
439 }
440 if (left == right && top == bottom) {
441 break; // single point on side OK
442 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000443 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000444 if (0 == corners) {
445 firstDirection = nextDirection;
446 first = last;
447 last = pts[-1];
448 corners = 1;
449 closedOrMoved = false;
450 break;
451 }
452 if (closedOrMoved) {
453 return false; // closed followed by a line
454 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000455 if (autoClose && nextDirection == firstDirection) {
456 break; // colinear with first
457 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 closedOrMoved = autoClose;
459 if (lastDirection != nextDirection) {
460 if (++corners > 4) {
461 return false; // too many direction changes
462 }
463 }
464 last = pts[-1];
465 if (lastDirection == nextDirection) {
466 break; // colinear segment
467 }
468 // Possible values for corners are 2, 3, and 4.
469 // When corners == 3, nextDirection opposes firstDirection.
470 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000471 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
473 if ((directionCycle ^ turn) != nextDirection) {
474 return false; // direction didn't follow cycle
475 }
476 break;
477 }
478 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000479 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 case kCubic_Verb:
481 return false; // quadratic, cubic not allowed
482 case kMove_Verb:
483 last = *pts++;
484 closedOrMoved = true;
485 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000486 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000487 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000488 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000490 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 lastDirection = nextDirection;
492 }
493 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000494 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000495 if (!result) {
496 // check if we are just an incomplete rectangle, in which case we can
497 // return true, but not claim to be closed.
498 // e.g.
499 // 3 sided rectangle
500 // 4 sided but the last edge is not long enough to reach the start
501 //
502 SkScalar closeX = first.x() - last.x();
503 SkScalar closeY = first.y() - last.y();
504 if (closeX && closeY) {
505 return false; // we're diagonal, abort (can we ever reach this?)
506 }
507 int closeDirection = rect_make_dir(closeX, closeY);
508 // make sure the close-segment doesn't double-back on itself
509 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
510 result = true;
511 autoClose = false; // we are not closed
512 }
513 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000514 if (savePts) {
515 *ptsPtr = savePts;
516 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000517 if (result && isClosed) {
518 *isClosed = autoClose;
519 }
520 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000521 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000522 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000523 return result;
524}
525
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000526SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
527 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000528 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
529 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000530 bool isClosed = false;
531 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
532}
533
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000534bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000535 SkDEBUGCODE(this->validate();)
536 int currVerb = 0;
537 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000538 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
539 if (result && rect) {
540 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000541 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000542 return result;
543}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000544
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000545bool SkPath::isRect(bool* isClosed, Direction* direction) const {
546 SkDEBUGCODE(this->validate();)
547 int currVerb = 0;
548 const SkPoint* pts = fPathRef->points();
549 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000550}
551
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000552bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000553 SkDEBUGCODE(this->validate();)
554 int currVerb = 0;
555 const SkPoint* pts = fPathRef->points();
556 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000557 Direction testDirs[2];
558 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return false;
560 }
561 const SkPoint* last = pts;
562 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000564 testRects[0].set(first, SkToS32(last - first));
565 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000566 if (testRects[0].contains(testRects[1])) {
567 if (rects) {
568 rects[0] = testRects[0];
569 rects[1] = testRects[1];
570 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000571 if (dirs) {
572 dirs[0] = testDirs[0];
573 dirs[1] = testDirs[1];
574 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000575 return true;
576 }
577 if (testRects[1].contains(testRects[0])) {
578 if (rects) {
579 rects[0] = testRects[1];
580 rects[1] = testRects[0];
581 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000582 if (dirs) {
583 dirs[0] = testDirs[1];
584 dirs[1] = testDirs[0];
585 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000586 return true;
587 }
588 }
589 return false;
590}
591
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000592int SkPath::countPoints() const {
593 return fPathRef->countPoints();
594}
595
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000596int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 SkDEBUGCODE(this->validate();)
598
599 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000600 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000601 int count = SkMin32(max, fPathRef->countPoints());
602 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
603 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604}
605
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000606SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000607 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
608 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000609 }
610 return SkPoint::Make(0, 0);
611}
612
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613int SkPath::countVerbs() const {
614 return fPathRef->countVerbs();
615}
616
617static inline void copy_verbs_reverse(uint8_t* inorderDst,
618 const uint8_t* reversedSrc,
619 int count) {
620 for (int i = 0; i < count; ++i) {
621 inorderDst[i] = reversedSrc[~i];
622 }
623}
624
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625int SkPath::getVerbs(uint8_t dst[], int max) const {
626 SkDEBUGCODE(this->validate();)
627
628 SkASSERT(max >= 0);
629 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 int count = SkMin32(max, fPathRef->countVerbs());
631 copy_verbs_reverse(dst, fPathRef->verbs(), count);
632 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000633}
634
reed@google.com294dd7b2011-10-11 11:58:32 +0000635bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 SkDEBUGCODE(this->validate();)
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000639 if (count > 0) {
640 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000641 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000643 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000645 if (lastPt) {
646 lastPt->set(0, 0);
647 }
648 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649}
650
651void SkPath::setLastPt(SkScalar x, SkScalar y) {
652 SkDEBUGCODE(this->validate();)
653
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 if (count == 0) {
656 this->moveTo(x, y);
657 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 SkPathRef::Editor ed(&fPathRef);
659 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 }
661}
662
reed@google.com04863fa2011-05-15 04:08:24 +0000663void SkPath::setConvexity(Convexity c) {
664 if (fConvexity != c) {
665 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000666 }
667}
668
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669//////////////////////////////////////////////////////////////////////////////
670// Construction methods
671
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000672#define DIRTY_AFTER_EDIT \
673 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000674 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000675 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000676 } while (0)
677
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678void SkPath::incReserve(U16CPU inc) {
679 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681 SkDEBUGCODE(this->validate();)
682}
683
684void SkPath::moveTo(SkScalar x, SkScalar y) {
685 SkDEBUGCODE(this->validate();)
686
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000687 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000689 // remember our index
690 fLastMoveToIndex = fPathRef->countPoints();
691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700693
694 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695}
696
697void SkPath::rMoveTo(SkScalar x, SkScalar y) {
698 SkPoint pt;
699 this->getLastPt(&pt);
700 this->moveTo(pt.fX + x, pt.fY + y);
701}
702
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000703void SkPath::injectMoveToIfNeeded() {
704 if (fLastMoveToIndex < 0) {
705 SkScalar x, y;
706 if (fPathRef->countVerbs() == 0) {
707 x = y = 0;
708 } else {
709 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
710 x = pt.fX;
711 y = pt.fY;
712 }
713 this->moveTo(x, y);
714 }
715}
716
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717void SkPath::lineTo(SkScalar x, SkScalar y) {
718 SkDEBUGCODE(this->validate();)
719
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000720 this->injectMoveToIfNeeded();
721
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 SkPathRef::Editor ed(&fPathRef);
723 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724
reed@google.comb54455e2011-05-16 14:16:04 +0000725 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726}
727
728void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000729 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730 SkPoint pt;
731 this->getLastPt(&pt);
732 this->lineTo(pt.fX + x, pt.fY + y);
733}
734
735void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
736 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000737
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000738 this->injectMoveToIfNeeded();
739
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 SkPathRef::Editor ed(&fPathRef);
741 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 pts[0].set(x1, y1);
743 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000744
reed@google.comb54455e2011-05-16 14:16:04 +0000745 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746}
747
748void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000749 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750 SkPoint pt;
751 this->getLastPt(&pt);
752 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
753}
754
reed@google.com277c3f82013-05-31 15:17:50 +0000755void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
756 SkScalar w) {
757 // check for <= 0 or NaN with this test
758 if (!(w > 0)) {
759 this->lineTo(x2, y2);
760 } else if (!SkScalarIsFinite(w)) {
761 this->lineTo(x1, y1);
762 this->lineTo(x2, y2);
763 } else if (SK_Scalar1 == w) {
764 this->quadTo(x1, y1, x2, y2);
765 } else {
766 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000767
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000768 this->injectMoveToIfNeeded();
769
reed@google.com277c3f82013-05-31 15:17:50 +0000770 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000771 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000772 pts[0].set(x1, y1);
773 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000774
reed@google.com277c3f82013-05-31 15:17:50 +0000775 DIRTY_AFTER_EDIT;
776 }
777}
778
779void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
780 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000781 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000782 SkPoint pt;
783 this->getLastPt(&pt);
784 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
785}
786
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
788 SkScalar x3, SkScalar y3) {
789 SkDEBUGCODE(this->validate();)
790
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000791 this->injectMoveToIfNeeded();
792
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 SkPathRef::Editor ed(&fPathRef);
794 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 pts[0].set(x1, y1);
796 pts[1].set(x2, y2);
797 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798
reed@google.comb54455e2011-05-16 14:16:04 +0000799 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800}
801
802void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
803 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000804 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 SkPoint pt;
806 this->getLastPt(&pt);
807 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
808 pt.fX + x3, pt.fY + y3);
809}
810
811void SkPath::close() {
812 SkDEBUGCODE(this->validate();)
813
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000814 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000816 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 case kLine_Verb:
818 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000819 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 case kMove_Verb: {
822 SkPathRef::Editor ed(&fPathRef);
823 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000825 }
reed@google.com277c3f82013-05-31 15:17:50 +0000826 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000827 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000828 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000829 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000830 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000831 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 }
833 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000834
835 // signal that we need a moveTo to follow us (unless we're done)
836#if 0
837 if (fLastMoveToIndex >= 0) {
838 fLastMoveToIndex = ~fLastMoveToIndex;
839 }
840#else
841 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
842#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843}
844
845///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000846
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000847static void assert_known_direction(int dir) {
848 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
849}
850
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851void SkPath::addRect(const SkRect& rect, Direction dir) {
852 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
853}
854
855void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
856 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000857 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000858 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
859 SkAutoDisableDirectionCheck addc(this);
860
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
862
863 this->incReserve(5);
864
865 this->moveTo(left, top);
866 if (dir == kCCW_Direction) {
867 this->lineTo(left, bottom);
868 this->lineTo(right, bottom);
869 this->lineTo(right, top);
870 } else {
871 this->lineTo(right, top);
872 this->lineTo(right, bottom);
873 this->lineTo(left, bottom);
874 }
875 this->close();
876}
877
reed@google.com744faba2012-05-29 19:54:52 +0000878void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
879 SkDEBUGCODE(this->validate();)
880 if (count <= 0) {
881 return;
882 }
883
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000884 fLastMoveToIndex = fPathRef->countPoints();
885
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000886 // +close makes room for the extra kClose_Verb
887 SkPathRef::Editor ed(&fPathRef, count+close, count);
888
889 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000890 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000891 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
892 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000893 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000894
reed@google.com744faba2012-05-29 19:54:52 +0000895 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000896 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000897 }
898
reed@google.com744faba2012-05-29 19:54:52 +0000899 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000900 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000901}
902
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000903#include "SkGeometry.h"
904
905static int build_arc_points(const SkRect& oval, SkScalar startAngle,
906 SkScalar sweepAngle,
907 SkPoint pts[kSkBuildQuadArcStorage]) {
908
909 if (0 == sweepAngle &&
910 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
911 // Chrome uses this path to move into and out of ovals. If not
912 // treated as a special case the moves can distort the oval's
913 // bounding box (and break the circle special case).
914 pts[0].set(oval.fRight, oval.centerY());
915 return 1;
916 } else if (0 == oval.width() && 0 == oval.height()) {
917 // Chrome will sometimes create 0 radius round rects. Having degenerate
918 // quad segments in the path prevents the path from being recognized as
919 // a rect.
920 // TODO: optimizing the case where only one of width or height is zero
921 // should also be considered. This case, however, doesn't seem to be
922 // as common as the single point case.
923 pts[0].set(oval.fRight, oval.fTop);
924 return 1;
925 }
926
927 SkVector start, stop;
928
929 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
930 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
931 &stop.fX);
932
933 /* If the sweep angle is nearly (but less than) 360, then due to precision
934 loss in radians-conversion and/or sin/cos, we may end up with coincident
935 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
936 of drawing a nearly complete circle (good).
937 e.g. canvas.drawArc(0, 359.99, ...)
938 -vs- canvas.drawArc(0, 359.9, ...)
939 We try to detect this edge case, and tweak the stop vector
940 */
941 if (start == stop) {
942 SkScalar sw = SkScalarAbs(sweepAngle);
943 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
944 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
945 // make a guess at a tiny angle (in radians) to tweak by
946 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
947 // not sure how much will be enough, so we use a loop
948 do {
949 stopRad -= deltaRad;
950 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
951 } while (start == stop);
952 }
953 }
954
955 SkMatrix matrix;
956
957 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
958 matrix.postTranslate(oval.centerX(), oval.centerY());
959
960 return SkBuildQuadArc(start, stop,
961 sweepAngle > 0 ? kCW_SkRotationDirection :
962 kCCW_SkRotationDirection,
963 &matrix, pts);
964}
965
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000966void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000968 SkRRect rrect;
969 rrect.setRectRadii(rect, (const SkVector*) radii);
970 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000971}
972
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000973/* The inline clockwise and counterclockwise round rect quad approximations
974 make it easier to see the symmetry patterns used by add corner quads.
975Clockwise corner value
976 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
977 path->quadTo(rect.fLeft, rect.fTop + offPtY,
978 rect.fLeft + midPtX, rect.fTop + midPtY);
979 path->quadTo(rect.fLeft + offPtX, rect.fTop,
980 rect.fLeft + rx, rect.fTop);
981
982 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
983 path->quadTo(rect.fRight - offPtX, rect.fTop,
984 rect.fRight - midPtX, rect.fTop + midPtY);
985 path->quadTo(rect.fRight, rect.fTop + offPtY,
986 rect.fRight, rect.fTop + ry);
987
988 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
989 path->quadTo(rect.fRight, rect.fBottom - offPtY,
990 rect.fRight - midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fRight - offPtX, rect.fBottom,
992 rect.fRight - rx, rect.fBottom);
993
994 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
995 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
996 rect.fLeft + midPtX, rect.fBottom - midPtY);
997 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
998 rect.fLeft, rect.fBottom - ry);
999
1000Counterclockwise
1001 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
1002 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
1003 rect.fLeft + midPtX, rect.fBottom - midPtY);
1004 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
1005 rect.fLeft + rx, rect.fBottom);
1006
1007 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
1008 path->quadTo(rect.fRight - offPtX, rect.fBottom,
1009 rect.fRight - midPtX, rect.fBottom - midPtY);
1010 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1011 rect.fRight, rect.fBottom - ry);
1012
1013 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1014 path->quadTo(rect.fRight, rect.fTop + offPtY,
1015 rect.fRight - midPtX, rect.fTop + midPtY);
1016 path->quadTo(rect.fRight - offPtX, rect.fTop,
1017 rect.fRight - rx, rect.fTop);
1018
1019 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1020 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1021 rect.fLeft + midPtX, rect.fTop + midPtY);
1022 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1023 rect.fLeft, rect.fTop + ry);
1024*/
1025static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1026 SkRRect::Corner corner, SkPath::Direction dir) {
1027 const SkRect& rect = rrect.rect();
1028 const SkVector& radii = rrect.radii(corner);
1029 SkScalar rx = radii.fX;
1030 SkScalar ry = radii.fY;
1031 // The mid point of the quadratic arc approximation is half way between the two
1032 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001033 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1034 SkScalar midPtX = rx * mid;
1035 SkScalar midPtY = ry * mid;
1036 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1037 SkScalar offPtX = rx * control;
1038 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001039 static const int kCornerPts = 5;
1040 SkScalar xOff[kCornerPts];
1041 SkScalar yOff[kCornerPts];
1042
1043 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1044 SkASSERT(dir == SkPath::kCCW_Direction
1045 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1046 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1047 xOff[0] = xOff[1] = 0;
1048 xOff[2] = midPtX;
1049 xOff[3] = offPtX;
1050 xOff[4] = rx;
1051 yOff[0] = ry;
1052 yOff[1] = offPtY;
1053 yOff[2] = midPtY;
1054 yOff[3] = yOff[4] = 0;
1055 } else {
1056 xOff[0] = rx;
1057 xOff[1] = offPtX;
1058 xOff[2] = midPtX;
1059 xOff[3] = xOff[4] = 0;
1060 yOff[0] = yOff[1] = 0;
1061 yOff[2] = midPtY;
1062 yOff[3] = offPtY;
1063 yOff[4] = ry;
1064 }
1065 if ((corner - 1) & 2) {
1066 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1067 for (int i = 0; i < kCornerPts; ++i) {
1068 xOff[i] = rect.fLeft + xOff[i];
1069 }
1070 } else {
1071 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1072 for (int i = 0; i < kCornerPts; ++i) {
1073 xOff[i] = rect.fRight - xOff[i];
1074 }
1075 }
1076 if (corner < SkRRect::kLowerRight_Corner) {
1077 for (int i = 0; i < kCornerPts; ++i) {
1078 yOff[i] = rect.fTop + yOff[i];
1079 }
1080 } else {
1081 for (int i = 0; i < kCornerPts; ++i) {
1082 yOff[i] = rect.fBottom - yOff[i];
1083 }
1084 }
1085
1086 SkPoint lastPt;
1087 SkAssertResult(path->getLastPt(&lastPt));
1088 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1089 path->lineTo(xOff[0], yOff[0]);
1090 }
1091 if (rx || ry) {
1092 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1093 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1094 } else {
1095 path->lineTo(xOff[2], yOff[2]);
1096 path->lineTo(xOff[4], yOff[4]);
1097 }
1098}
1099
reed@google.com4ed0fb72012-12-12 20:48:18 +00001100void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001101 assert_known_direction(dir);
1102
1103 if (rrect.isEmpty()) {
1104 return;
1105 }
1106
reed@google.com4ed0fb72012-12-12 20:48:18 +00001107 const SkRect& bounds = rrect.getBounds();
1108
1109 if (rrect.isRect()) {
1110 this->addRect(bounds, dir);
1111 } else if (rrect.isOval()) {
1112 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001113 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001114 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001116 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001117 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001118
1119 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001120 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001121 this->moveTo(bounds.fLeft,
1122 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1123 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1124 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1125 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1126 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001127 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001128 this->moveTo(bounds.fLeft,
1129 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1130 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1131 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1132 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1133 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001134 }
1135 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001136 }
reed5bcbe912014-12-15 12:28:33 -08001137 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001138}
1139
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001140bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001141 int count = fPathRef->countVerbs();
1142 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1143 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001144 if (*verbs == kLine_Verb ||
1145 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001146 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001147 *verbs == kCubic_Verb) {
1148 return false;
1149 }
1150 ++verbs;
1151 }
1152 return true;
1153}
1154
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001155void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1156 Direction dir) {
1157 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001158
humper@google.com75e3ca12013-04-08 21:44:11 +00001159 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001160 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001161 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001162 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001163 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1164 return;
1165 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001166
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001167 SkRRect rrect;
1168 rrect.setRectXY(rect, rx, ry);
1169 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001170}
1171
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001173 assert_known_direction(dir);
1174
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001175 /* If addOval() is called after previous moveTo(),
1176 this path is still marked as an oval. This is used to
1177 fit into WebKit's calling sequences.
1178 We can't simply check isEmpty() in this case, as additional
1179 moveTo() would mark the path non empty.
1180 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001181 bool isOval = hasOnlyMoveTos();
1182 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001183 fDirection = dir;
1184 } else {
1185 fDirection = kUnknown_Direction;
1186 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001187
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001188 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001189
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190 SkAutoPathBoundsUpdate apbu(this, oval);
1191
reed220f9262014-12-17 08:21:04 -08001192#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 SkScalar cx = oval.centerX();
1194 SkScalar cy = oval.centerY();
1195 SkScalar rx = SkScalarHalf(oval.width());
1196 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1199 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1200 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1201 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1202
1203 /*
reed220f9262014-12-17 08:21:04 -08001204 To handle imprecision in computing the center and radii, we revert to
1205 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1206 to ensure that we don't exceed the oval's bounds *ever*, since we want
1207 to use oval for our fast-bounds, rather than have to recompute it.
1208 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 const SkScalar L = oval.fLeft; // cx - rx
1210 const SkScalar T = oval.fTop; // cy - ry
1211 const SkScalar R = oval.fRight; // cx + rx
1212 const SkScalar B = oval.fBottom; // cy + ry
1213
1214 this->incReserve(17); // 8 quads + close
1215 this->moveTo(R, cy);
1216 if (dir == kCCW_Direction) {
1217 this->quadTo( R, cy - sy, cx + mx, cy - my);
1218 this->quadTo(cx + sx, T, cx , T);
1219 this->quadTo(cx - sx, T, cx - mx, cy - my);
1220 this->quadTo( L, cy - sy, L, cy );
1221 this->quadTo( L, cy + sy, cx - mx, cy + my);
1222 this->quadTo(cx - sx, B, cx , B);
1223 this->quadTo(cx + sx, B, cx + mx, cy + my);
1224 this->quadTo( R, cy + sy, R, cy );
1225 } else {
1226 this->quadTo( R, cy + sy, cx + mx, cy + my);
1227 this->quadTo(cx + sx, B, cx , B);
1228 this->quadTo(cx - sx, B, cx - mx, cy + my);
1229 this->quadTo( L, cy + sy, L, cy );
1230 this->quadTo( L, cy - sy, cx - mx, cy - my);
1231 this->quadTo(cx - sx, T, cx , T);
1232 this->quadTo(cx + sx, T, cx + mx, cy - my);
1233 this->quadTo( R, cy - sy, R, cy );
1234 }
reed220f9262014-12-17 08:21:04 -08001235#else
1236 const SkScalar L = oval.fLeft;
1237 const SkScalar T = oval.fTop;
1238 const SkScalar R = oval.fRight;
1239 const SkScalar B = oval.fBottom;
1240 const SkScalar cx = oval.centerX();
1241 const SkScalar cy = oval.centerY();
1242 const SkScalar weight = SK_ScalarRoot2Over2;
1243
1244 this->incReserve(9); // move + 4 conics
1245 this->moveTo(R, cy);
1246 if (dir == kCCW_Direction) {
1247 this->conicTo(R, T, cx, T, weight);
1248 this->conicTo(L, T, L, cy, weight);
1249 this->conicTo(L, B, cx, B, weight);
1250 this->conicTo(R, B, R, cy, weight);
1251 } else {
1252 this->conicTo(R, B, cx, B, weight);
1253 this->conicTo(L, B, L, cy, weight);
1254 this->conicTo(L, T, cx, T, weight);
1255 this->conicTo(R, T, R, cy, weight);
1256 }
1257#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259
robertphillips@google.com466310d2013-12-03 16:43:54 +00001260 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001261
robertphillips@google.com466310d2013-12-03 16:43:54 +00001262 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001263}
1264
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1266 if (r > 0) {
1267 SkRect rect;
1268 rect.set(x - r, y - r, x + r, y + r);
1269 this->addOval(rect, dir);
1270 }
1271}
1272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1274 bool forceMoveTo) {
1275 if (oval.width() < 0 || oval.height() < 0) {
1276 return;
1277 }
1278
1279 SkPoint pts[kSkBuildQuadArcStorage];
1280 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1281 SkASSERT((count & 1) == 1);
1282
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001283 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 forceMoveTo = true;
1285 }
1286 this->incReserve(count);
1287 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1288 for (int i = 1; i < count; i += 2) {
1289 this->quadTo(pts[i], pts[i+1]);
1290 }
1291}
1292
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001293void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 if (oval.isEmpty() || 0 == sweepAngle) {
1295 return;
1296 }
1297
1298 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1299
1300 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1301 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1302 return;
1303 }
1304
1305 SkPoint pts[kSkBuildQuadArcStorage];
1306 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1307
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001308 SkDEBUGCODE(this->validate();)
1309 SkASSERT(count & 1);
1310
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001311 fLastMoveToIndex = fPathRef->countPoints();
1312
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001313 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1314
1315 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1316 if (count > 1) {
1317 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1318 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001320
1321 DIRTY_AFTER_EDIT;
1322 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323}
1324
1325/*
1326 Need to handle the case when the angle is sharp, and our computed end-points
1327 for the arc go behind pt1 and/or p2...
1328*/
1329void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1330 SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001331 if (radius == 0) {
1332 this->lineTo(x1, y1);
1333 return;
1334 }
1335
1336 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001337
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 // need to know our prev pt so we can construct tangent vectors
1339 {
1340 SkPoint start;
1341 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001342 // Handle degenerate cases by adding a line to the first point and
1343 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 before.setNormalize(x1 - start.fX, y1 - start.fY);
1345 after.setNormalize(x2 - x1, y2 - y1);
1346 }
reed@google.comabf15c12011-01-18 20:35:51 +00001347
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 SkScalar cosh = SkPoint::DotProduct(before, after);
1349 SkScalar sinh = SkPoint::CrossProduct(before, after);
1350
1351 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001352 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001353 return;
1354 }
reed@google.comabf15c12011-01-18 20:35:51 +00001355
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1357 if (dist < 0) {
1358 dist = -dist;
1359 }
1360
1361 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1362 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1363 SkRotationDirection arcDir;
1364
1365 // now turn before/after into normals
1366 if (sinh > 0) {
1367 before.rotateCCW();
1368 after.rotateCCW();
1369 arcDir = kCW_SkRotationDirection;
1370 } else {
1371 before.rotateCW();
1372 after.rotateCW();
1373 arcDir = kCCW_SkRotationDirection;
1374 }
1375
1376 SkMatrix matrix;
1377 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001378
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379 matrix.setScale(radius, radius);
1380 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1381 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001382
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 this->incReserve(count);
1386 // [xx,yy] == pts[0]
1387 this->lineTo(xx, yy);
1388 for (int i = 1; i < count; i += 2) {
1389 this->quadTo(pts[i], pts[i+1]);
1390 }
1391}
1392
1393///////////////////////////////////////////////////////////////////////////////
1394
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001395void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 SkMatrix matrix;
1397
1398 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001399 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400}
1401
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001402void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001403 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001405 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406 SkPoint pts[4];
1407 Verb verb;
1408
1409 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001410 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 while ((verb = iter.next(pts)) != kDone_Verb) {
1412 switch (verb) {
1413 case kMove_Verb:
1414 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001415 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1416 injectMoveToIfNeeded(); // In case last contour is closed
1417 this->lineTo(pts[0]);
1418 } else {
1419 this->moveTo(pts[0]);
1420 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421 break;
1422 case kLine_Verb:
1423 proc(matrix, &pts[1], &pts[1], 1);
1424 this->lineTo(pts[1]);
1425 break;
1426 case kQuad_Verb:
1427 proc(matrix, &pts[1], &pts[1], 2);
1428 this->quadTo(pts[1], pts[2]);
1429 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001430 case kConic_Verb:
1431 proc(matrix, &pts[1], &pts[1], 2);
1432 this->conicTo(pts[1], pts[2], iter.conicWeight());
1433 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 case kCubic_Verb:
1435 proc(matrix, &pts[1], &pts[1], 3);
1436 this->cubicTo(pts[1], pts[2], pts[3]);
1437 break;
1438 case kClose_Verb:
1439 this->close();
1440 break;
1441 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001442 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001444 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 }
1446}
1447
1448///////////////////////////////////////////////////////////////////////////////
1449
reed@google.com277c3f82013-05-31 15:17:50 +00001450static int pts_in_verb(unsigned verb) {
1451 static const uint8_t gPtsInVerb[] = {
1452 1, // kMove
1453 1, // kLine
1454 2, // kQuad
1455 2, // kConic
1456 3, // kCubic
1457 0, // kClose
1458 0 // kDone
1459 };
1460
1461 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1462 return gPtsInVerb[verb];
1463}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465// ignore the last point of the 1st contour
1466void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001467 int i, vcount = path.fPathRef->countVerbs();
1468 // exit early if the path is empty, or just has a moveTo.
1469 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 return;
1471 }
1472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001475 const uint8_t* verbs = path.fPathRef->verbs();
1476 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001477 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001479 SkASSERT(verbs[~0] == kMove_Verb);
1480 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001481 unsigned v = verbs[~i];
1482 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 if (n == 0) {
1484 break;
1485 }
1486 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001487 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 }
1489
1490 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001491 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 case kLine_Verb:
1493 this->lineTo(pts[-1].fX, pts[-1].fY);
1494 break;
1495 case kQuad_Verb:
1496 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1497 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001498 case kConic_Verb:
1499 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1500 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 case kCubic_Verb:
1502 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1503 pts[-3].fX, pts[-3].fY);
1504 break;
1505 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001506 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 break;
1508 }
reed@google.com277c3f82013-05-31 15:17:50 +00001509 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 }
1511}
1512
reed@google.com63d73742012-01-10 15:33:12 +00001513void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001515
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001516 const SkPoint* pts = src.fPathRef->pointsEnd();
1517 // we will iterator through src's verbs backwards
1518 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1519 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001520 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001521
1522 bool needMove = true;
1523 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 while (verbs < verbsEnd) {
1525 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001526 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001527
1528 if (needMove) {
1529 --pts;
1530 this->moveTo(pts->fX, pts->fY);
1531 needMove = false;
1532 }
1533 pts -= n;
1534 switch (v) {
1535 case kMove_Verb:
1536 if (needClose) {
1537 this->close();
1538 needClose = false;
1539 }
1540 needMove = true;
1541 pts += 1; // so we see the point in "if (needMove)" above
1542 break;
1543 case kLine_Verb:
1544 this->lineTo(pts[0]);
1545 break;
1546 case kQuad_Verb:
1547 this->quadTo(pts[1], pts[0]);
1548 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001549 case kConic_Verb:
1550 this->conicTo(pts[1], pts[0], *--conicWeights);
1551 break;
reed@google.com63d73742012-01-10 15:33:12 +00001552 case kCubic_Verb:
1553 this->cubicTo(pts[2], pts[1], pts[0]);
1554 break;
1555 case kClose_Verb:
1556 needClose = true;
1557 break;
1558 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001559 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001560 }
1561 }
1562}
1563
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564///////////////////////////////////////////////////////////////////////////////
1565
1566void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1567 SkMatrix matrix;
1568
1569 matrix.setTranslate(dx, dy);
1570 this->transform(matrix, dst);
1571}
1572
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1574 int level = 2) {
1575 if (--level >= 0) {
1576 SkPoint tmp[7];
1577
1578 SkChopCubicAtHalf(pts, tmp);
1579 subdivide_cubic_to(path, &tmp[0], level);
1580 subdivide_cubic_to(path, &tmp[3], level);
1581 } else {
1582 path->cubicTo(pts[1], pts[2], pts[3]);
1583 }
1584}
1585
1586void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1587 SkDEBUGCODE(this->validate();)
1588 if (dst == NULL) {
1589 dst = (SkPath*)this;
1590 }
1591
tomhudson@google.com8d430182011-06-06 19:11:19 +00001592 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593 SkPath tmp;
1594 tmp.fFillType = fFillType;
1595
1596 SkPath::Iter iter(*this, false);
1597 SkPoint pts[4];
1598 SkPath::Verb verb;
1599
reed@google.com4a3b7142012-05-16 17:16:46 +00001600 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 switch (verb) {
1602 case kMove_Verb:
1603 tmp.moveTo(pts[0]);
1604 break;
1605 case kLine_Verb:
1606 tmp.lineTo(pts[1]);
1607 break;
1608 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001609 // promote the quad to a conic
1610 tmp.conicTo(pts[1], pts[2],
1611 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001613 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001614 tmp.conicTo(pts[1], pts[2],
1615 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001616 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 case kCubic_Verb:
1618 subdivide_cubic_to(&tmp, pts);
1619 break;
1620 case kClose_Verb:
1621 tmp.close();
1622 break;
1623 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001624 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 break;
1626 }
1627 }
1628
1629 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001630 SkPathRef::Editor ed(&dst->fPathRef);
1631 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001632 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001634 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1635
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001638 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001639 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001641
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001642 if (kUnknown_Direction == fDirection) {
1643 dst->fDirection = kUnknown_Direction;
1644 } else {
1645 SkScalar det2x2 =
1646 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1647 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1648 if (det2x2 < 0) {
1649 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1650 } else if (det2x2 > 0) {
1651 dst->fDirection = fDirection;
1652 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001653 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001654 dst->fDirection = kUnknown_Direction;
1655 }
1656 }
1657
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 SkDEBUGCODE(dst->validate();)
1659 }
1660}
1661
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662///////////////////////////////////////////////////////////////////////////////
1663///////////////////////////////////////////////////////////////////////////////
1664
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001665enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001666 kEmptyContour_SegmentState, // The current contour is empty. We may be
1667 // starting processing or we may have just
1668 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001669 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1670 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1671 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672};
1673
1674SkPath::Iter::Iter() {
1675#ifdef SK_DEBUG
1676 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001677 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001679 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001680 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681#endif
1682 // need to init enough to make next() harmlessly return kDone_Verb
1683 fVerbs = NULL;
1684 fVerbStop = NULL;
1685 fNeedClose = false;
1686}
1687
1688SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1689 this->setPath(path, forceClose);
1690}
1691
1692void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 fPts = path.fPathRef->points();
1694 fVerbs = path.fPathRef->verbs();
1695 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001696 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001698 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 fForceClose = SkToU8(forceClose);
1700 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001701 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702}
1703
1704bool SkPath::Iter::isClosedContour() const {
1705 if (fVerbs == NULL || fVerbs == fVerbStop) {
1706 return false;
1707 }
1708 if (fForceClose) {
1709 return true;
1710 }
1711
1712 const uint8_t* verbs = fVerbs;
1713 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001714
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001715 if (kMove_Verb == *(verbs - 1)) {
1716 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 }
1718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001719 while (verbs > stop) {
1720 // verbs points one beyond the current verb, decrement first.
1721 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 if (kMove_Verb == v) {
1723 break;
1724 }
1725 if (kClose_Verb == v) {
1726 return true;
1727 }
1728 }
1729 return false;
1730}
1731
1732SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001733 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001735 // A special case: if both points are NaN, SkPoint::operation== returns
1736 // false, but the iterator expects that they are treated as the same.
1737 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001738 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1739 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001740 return kClose_Verb;
1741 }
1742
reed@google.com9e25dbf2012-05-15 17:05:38 +00001743 pts[0] = fLastPt;
1744 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 fLastPt = fMoveTo;
1746 fCloseLine = true;
1747 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001748 } else {
1749 pts[0] = fMoveTo;
1750 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752}
1753
reed@google.com9e25dbf2012-05-15 17:05:38 +00001754const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001755 if (fSegmentState == kAfterMove_SegmentState) {
1756 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001758 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1761 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001762 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764}
1765
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766void SkPath::Iter::consumeDegenerateSegments() {
1767 // We need to step over anything that will not move the current draw point
1768 // forward before the next move is seen
1769 const uint8_t* lastMoveVerb = 0;
1770 const SkPoint* lastMovePt = 0;
1771 SkPoint lastPt = fLastPt;
1772 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 switch (verb) {
1775 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001776 // Keep a record of this most recent move
1777 lastMoveVerb = fVerbs;
1778 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001779 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001780 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001781 fPts++;
1782 break;
1783
1784 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001785 // A close when we are in a segment is always valid except when it
1786 // follows a move which follows a segment.
1787 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 return;
1789 }
1790 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001791 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 break;
1793
1794 case kLine_Verb:
1795 if (!IsLineDegenerate(lastPt, fPts[0])) {
1796 if (lastMoveVerb) {
1797 fVerbs = lastMoveVerb;
1798 fPts = lastMovePt;
1799 return;
1800 }
1801 return;
1802 }
1803 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001804 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001805 fPts++;
1806 break;
1807
reed@google.com277c3f82013-05-31 15:17:50 +00001808 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 case kQuad_Verb:
1810 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1811 if (lastMoveVerb) {
1812 fVerbs = lastMoveVerb;
1813 fPts = lastMovePt;
1814 return;
1815 }
1816 return;
1817 }
1818 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001821 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 break;
1823
1824 case kCubic_Verb:
1825 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1826 if (lastMoveVerb) {
1827 fVerbs = lastMoveVerb;
1828 fPts = lastMovePt;
1829 return;
1830 }
1831 return;
1832 }
1833 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001834 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 fPts += 3;
1836 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001837
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001839 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 }
1841 }
1842}
1843
reed@google.com4a3b7142012-05-16 17:16:46 +00001844SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001845 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 // Close the curve if requested and if there is some curve to close
1849 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001850 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 return kLine_Verb;
1852 }
1853 fNeedClose = false;
1854 return kClose_Verb;
1855 }
1856 return kDone_Verb;
1857 }
1858
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001859 // fVerbs is one beyond the current verb, decrement first
1860 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001861 const SkPoint* SK_RESTRICT srcPts = fPts;
1862 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863
1864 switch (verb) {
1865 case kMove_Verb:
1866 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 verb = this->autoClose(pts);
1869 if (verb == kClose_Verb) {
1870 fNeedClose = false;
1871 }
1872 return (Verb)verb;
1873 }
1874 if (fVerbs == fVerbStop) { // might be a trailing moveto
1875 return kDone_Verb;
1876 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001877 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001878 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001880 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001881 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 fNeedClose = fForceClose;
1883 break;
1884 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001885 pts[0] = this->cons_moveTo();
1886 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 fLastPt = srcPts[0];
1888 fCloseLine = false;
1889 srcPts += 1;
1890 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001891 case kConic_Verb:
1892 fConicWeights += 1;
1893 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001895 pts[0] = this->cons_moveTo();
1896 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 fLastPt = srcPts[1];
1898 srcPts += 2;
1899 break;
1900 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 pts[0] = this->cons_moveTo();
1902 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 fLastPt = srcPts[2];
1904 srcPts += 3;
1905 break;
1906 case kClose_Verb:
1907 verb = this->autoClose(pts);
1908 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001909 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 } else {
1911 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001912 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 break;
1916 }
1917 fPts = srcPts;
1918 return (Verb)verb;
1919}
1920
1921///////////////////////////////////////////////////////////////////////////////
1922
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001923SkPath::RawIter::RawIter() {
1924#ifdef SK_DEBUG
1925 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001926 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001927 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1928#endif
1929 // need to init enough to make next() harmlessly return kDone_Verb
1930 fVerbs = NULL;
1931 fVerbStop = NULL;
1932}
1933
1934SkPath::RawIter::RawIter(const SkPath& path) {
1935 this->setPath(path);
1936}
1937
1938void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001939 fPts = path.fPathRef->points();
1940 fVerbs = path.fPathRef->verbs();
1941 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001942 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001943 fMoveTo.fX = fMoveTo.fY = 0;
1944 fLastPt.fX = fLastPt.fY = 0;
1945}
1946
1947SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001948 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001949 if (fVerbs == fVerbStop) {
1950 return kDone_Verb;
1951 }
1952
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001953 // fVerbs points one beyond next verb so decrement first.
1954 unsigned verb = *(--fVerbs);
1955 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001956
1957 switch (verb) {
1958 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001959 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001960 fMoveTo = srcPts[0];
1961 fLastPt = fMoveTo;
1962 srcPts += 1;
1963 break;
1964 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001965 pts[0] = fLastPt;
1966 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001967 fLastPt = srcPts[0];
1968 srcPts += 1;
1969 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001970 case kConic_Verb:
1971 fConicWeights += 1;
1972 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001973 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001974 pts[0] = fLastPt;
1975 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001976 fLastPt = srcPts[1];
1977 srcPts += 2;
1978 break;
1979 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001980 pts[0] = fLastPt;
1981 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001982 fLastPt = srcPts[2];
1983 srcPts += 3;
1984 break;
1985 case kClose_Verb:
1986 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001987 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001988 break;
1989 }
1990 fPts = srcPts;
1991 return (Verb)verb;
1992}
1993
1994///////////////////////////////////////////////////////////////////////////////
1995
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001997 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998*/
1999
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002000size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 SkDEBUGCODE(this->validate();)
2002
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002003 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002004 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002005 return SkAlign4(byteCount);
2006 }
2007
2008 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
robertphillips@google.com466310d2013-12-03 16:43:54 +00002010 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002012 (fDirection << kDirection_SerializationShift) |
2013 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002014
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002015 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002016
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002017 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002018
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002019 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002020 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021}
2022
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002023size_t SkPath::readFromMemory(const void* storage, size_t length) {
2024 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002025
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002026 int32_t packed;
2027 if (!buffer.readS32(&packed)) {
2028 return 0;
2029 }
2030
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002031 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2032 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002033 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002034 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002035 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002036
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002037 size_t sizeRead = 0;
2038 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002039 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002040 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002041 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002042 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002043 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002044 // If the buffer is not valid, pathRef should be NULL
2045 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002046 }
2047 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048}
2049
2050///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051
reede05fed02014-12-15 07:59:53 -08002052#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002053#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002054
reed@google.com51bbe752013-01-17 22:07:50 +00002055static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002056 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002057 str->append(label);
2058 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002059
reed@google.com51bbe752013-01-17 22:07:50 +00002060 const SkScalar* values = &pts[0].fX;
2061 count *= 2;
2062
2063 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002064 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002065 if (i < count - 1) {
2066 str->append(", ");
2067 }
2068 }
reed@google.com277c3f82013-05-31 15:17:50 +00002069 if (conicWeight >= 0) {
2070 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002071 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002072 }
caryclark08fa28c2014-10-23 13:08:56 -07002073 str->append(");");
reede05fed02014-12-15 07:59:53 -08002074 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002075 str->append(" // ");
2076 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002077 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002078 if (i < count - 1) {
2079 str->append(", ");
2080 }
2081 }
2082 if (conicWeight >= 0) {
2083 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002084 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002085 }
2086 }
2087 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002088}
2089
caryclarke9562592014-09-15 09:26:09 -07002090void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002091 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002092 Iter iter(*this, forceClose);
2093 SkPoint pts[4];
2094 Verb verb;
2095
caryclark66a5d8b2014-06-24 08:30:15 -07002096 if (!wStream) {
2097 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2098 }
reed@google.com51bbe752013-01-17 22:07:50 +00002099 SkString builder;
2100
reed@google.com4a3b7142012-05-16 17:16:46 +00002101 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 switch (verb) {
2103 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002104 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 break;
2106 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002107 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002108 break;
2109 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002110 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002111 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002112 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002113 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002114 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002116 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 break;
2118 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002119 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 break;
2121 default:
2122 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2123 verb = kDone_Verb; // stop the loop
2124 break;
2125 }
2126 }
caryclark66a5d8b2014-06-24 08:30:15 -07002127 if (wStream) {
2128 wStream->writeText(builder.c_str());
2129 } else {
2130 SkDebugf("%s", builder.c_str());
2131 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132}
2133
reed@android.come522ca52009-11-23 20:10:41 +00002134void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002135 this->dump(NULL, false, false);
2136}
2137
2138void SkPath::dumpHex() const {
2139 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002140}
2141
2142#ifdef SK_DEBUG
2143void SkPath::validate() const {
2144 SkASSERT(this != NULL);
2145 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002146
djsollen@google.com077348c2012-10-22 20:23:32 +00002147#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002148 if (!fBoundsIsDirty) {
2149 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002150
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002151 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002152 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002153
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002154 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002155 // if we're empty, fBounds may be empty but translated, so we can't
2156 // necessarily compare to bounds directly
2157 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2158 // be [2, 2, 2, 2]
2159 SkASSERT(bounds.isEmpty());
2160 SkASSERT(fBounds.isEmpty());
2161 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002162 if (bounds.isEmpty()) {
2163 SkASSERT(fBounds.isEmpty());
2164 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002165 if (!fBounds.isEmpty()) {
2166 SkASSERT(fBounds.contains(bounds));
2167 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002168 }
reed@android.come522ca52009-11-23 20:10:41 +00002169 }
2170 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002171#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002172}
djsollen@google.com077348c2012-10-22 20:23:32 +00002173#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002174
reed@google.com04863fa2011-05-15 04:08:24 +00002175///////////////////////////////////////////////////////////////////////////////
2176
reed@google.com0b7b9822011-05-16 12:29:27 +00002177static int sign(SkScalar x) { return x < 0; }
2178#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002179
robertphillipsc506e302014-09-16 09:43:31 -07002180enum DirChange {
2181 kLeft_DirChange,
2182 kRight_DirChange,
2183 kStraight_DirChange,
2184 kBackwards_DirChange,
2185
2186 kInvalid_DirChange
2187};
2188
2189
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002190static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002191 // The error epsilon was empirically derived; worse case round rects
2192 // with a mid point outset by 2x float epsilon in tests had an error
2193 // of 12.
2194 const int epsilon = 16;
2195 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2196 return false;
2197 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002198 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002199 int aBits = SkFloatAs2sCompliment(compA);
2200 int bBits = SkFloatAs2sCompliment(compB);
2201 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002202}
2203
robertphillipsc506e302014-09-16 09:43:31 -07002204static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2205 const SkVector& lastVec, const SkVector& curVec) {
2206 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2207
2208 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2209 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2210 largest = SkTMax(largest, -smallest);
2211
2212 if (!almost_equal(largest, largest + cross)) {
2213 int sign = SkScalarSignAsInt(cross);
2214 if (sign) {
2215 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2216 }
2217 }
2218
2219 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2220 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2221 lastVec.dot(curVec) < 0.0f) {
2222 return kBackwards_DirChange;
2223 }
2224
2225 return kStraight_DirChange;
2226}
2227
reed@google.com04863fa2011-05-15 04:08:24 +00002228// only valid for a single contour
2229struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002230 Convexicator()
2231 : fPtCount(0)
2232 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002233 , fDirection(SkPath::kUnknown_Direction)
2234 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002235 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002238 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002239 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002240 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002241
2242 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002243 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002244 }
2245
2246 SkPath::Convexity getConvexity() const { return fConvexity; }
2247
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002248 /** The direction returned is only valid if the path is determined convex */
2249 SkPath::Direction getDirection() const { return fDirection; }
2250
reed@google.com04863fa2011-05-15 04:08:24 +00002251 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002252 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002253 return;
2254 }
2255
2256 if (0 == fPtCount) {
2257 fCurrPt = pt;
2258 ++fPtCount;
2259 } else {
2260 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002261 SkScalar lengthSqd = vec.lengthSqd();
2262 if (!SkScalarIsFinite(lengthSqd)) {
2263 fIsFinite = false;
2264 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002265 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 fCurrPt = pt;
2267 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002268 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002269 } else {
2270 SkASSERT(fPtCount > 2);
2271 this->addVec(vec);
2272 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002273
reed@google.com85b6e392011-05-15 20:25:17 +00002274 int sx = sign(vec.fX);
2275 int sy = sign(vec.fY);
2276 fDx += (sx != fSx);
2277 fDy += (sy != fSy);
2278 fSx = sx;
2279 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002280
reed@google.com85b6e392011-05-15 20:25:17 +00002281 if (fDx > 3 || fDy > 3) {
2282 fConvexity = SkPath::kConcave_Convexity;
2283 }
reed@google.com04863fa2011-05-15 04:08:24 +00002284 }
2285 }
2286 }
2287
2288 void close() {
2289 if (fPtCount > 2) {
2290 this->addVec(fFirstVec);
2291 }
2292 }
2293
caryclarkd3d1a982014-12-08 04:57:38 -08002294 bool isFinite() const {
2295 return fIsFinite;
2296 }
2297
reed@google.com04863fa2011-05-15 04:08:24 +00002298private:
2299 void addVec(const SkVector& vec) {
2300 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002301 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2302 switch (dir) {
2303 case kLeft_DirChange: // fall through
2304 case kRight_DirChange:
2305 if (kInvalid_DirChange == fExpectedDir) {
2306 fExpectedDir = dir;
2307 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2308 : SkPath::kCCW_Direction;
2309 } else if (dir != fExpectedDir) {
2310 fConvexity = SkPath::kConcave_Convexity;
2311 fDirection = SkPath::kUnknown_Direction;
2312 }
2313 fLastVec = vec;
2314 break;
2315 case kStraight_DirChange:
2316 break;
2317 case kBackwards_DirChange:
2318 fLastVec = vec;
2319 break;
2320 case kInvalid_DirChange:
2321 SkFAIL("Use of invalid direction change flag");
2322 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002323 }
2324 }
2325
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002326 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002327 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002328 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2329 // value with the current vec is deemed to be of a significant value.
2330 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002331 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002332 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002333 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002335 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002336 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002337};
2338
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002339SkPath::Convexity SkPath::internalGetConvexity() const {
2340 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002341 SkPoint pts[4];
2342 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002343 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002344
2345 int contourCount = 0;
2346 int count;
2347 Convexicator state;
2348
caryclarkd3d1a982014-12-08 04:57:38 -08002349 if (!isFinite()) {
2350 return kUnknown_Convexity;
2351 }
reed@google.com04863fa2011-05-15 04:08:24 +00002352 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2353 switch (verb) {
2354 case kMove_Verb:
2355 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002357 return kConcave_Convexity;
2358 }
2359 pts[1] = pts[0];
2360 count = 1;
2361 break;
2362 case kLine_Verb: count = 1; break;
2363 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002364 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002365 case kCubic_Verb: count = 3; break;
2366 case kClose_Verb:
2367 state.close();
2368 count = 0;
2369 break;
2370 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002371 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002372 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002373 return kConcave_Convexity;
2374 }
2375
2376 for (int i = 1; i <= count; i++) {
2377 state.addPt(pts[i]);
2378 }
2379 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002380 if (!state.isFinite()) {
2381 return kUnknown_Convexity;
2382 }
reed@google.com04863fa2011-05-15 04:08:24 +00002383 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002384 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002385 return kConcave_Convexity;
2386 }
2387 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 fConvexity = state.getConvexity();
2389 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2390 fDirection = state.getDirection();
2391 }
2392 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002393}
reed@google.com69a99432012-01-10 18:00:10 +00002394
2395///////////////////////////////////////////////////////////////////////////////
2396
2397class ContourIter {
2398public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002399 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002400
2401 bool done() const { return fDone; }
2402 // if !done() then these may be called
2403 int count() const { return fCurrPtCount; }
2404 const SkPoint* pts() const { return fCurrPt; }
2405 void next();
2406
2407private:
2408 int fCurrPtCount;
2409 const SkPoint* fCurrPt;
2410 const uint8_t* fCurrVerb;
2411 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002412 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002413 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002414 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002415};
2416
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002417ContourIter::ContourIter(const SkPathRef& pathRef) {
2418 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002419 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002420 fCurrPt = pathRef.points();
2421 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002422 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002423 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002424 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002425 this->next();
2426}
2427
2428void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002429 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002430 fDone = true;
2431 }
2432 if (fDone) {
2433 return;
2434 }
2435
2436 // skip pts of prev contour
2437 fCurrPt += fCurrPtCount;
2438
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002439 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002440 int ptCount = 1; // moveTo
2441 const uint8_t* verbs = fCurrVerb;
2442
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002443 for (--verbs; verbs > fStopVerbs; --verbs) {
2444 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002445 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002446 goto CONTOUR_END;
2447 case SkPath::kLine_Verb:
2448 ptCount += 1;
2449 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002450 case SkPath::kConic_Verb:
2451 fCurrConicWeight += 1;
2452 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002453 case SkPath::kQuad_Verb:
2454 ptCount += 2;
2455 break;
2456 case SkPath::kCubic_Verb:
2457 ptCount += 3;
2458 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002459 case SkPath::kClose_Verb:
2460 break;
2461 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002462 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002463 break;
2464 }
2465 }
2466CONTOUR_END:
2467 fCurrPtCount = ptCount;
2468 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002469 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002470}
2471
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002472// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002473static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002474 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2475 // We may get 0 when the above subtracts underflow. We expect this to be
2476 // very rare and lazily promote to double.
2477 if (0 == cross) {
2478 double p0x = SkScalarToDouble(p0.fX);
2479 double p0y = SkScalarToDouble(p0.fY);
2480
2481 double p1x = SkScalarToDouble(p1.fX);
2482 double p1y = SkScalarToDouble(p1.fY);
2483
2484 double p2x = SkScalarToDouble(p2.fX);
2485 double p2y = SkScalarToDouble(p2.fY);
2486
2487 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2488 (p1y - p0y) * (p2x - p0x));
2489
2490 }
2491 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002492}
2493
reed@google.comc1ea60a2012-01-31 15:15:36 +00002494// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002495static int find_max_y(const SkPoint pts[], int count) {
2496 SkASSERT(count > 0);
2497 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002498 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002499 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002500 SkScalar y = pts[i].fY;
2501 if (y > max) {
2502 max = y;
2503 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002504 }
2505 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002506 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002507}
2508
reed@google.comcabaf1d2012-01-11 21:03:05 +00002509static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2510 int i = index;
2511 for (;;) {
2512 i = (i + inc) % n;
2513 if (i == index) { // we wrapped around, so abort
2514 break;
2515 }
2516 if (pts[index] != pts[i]) { // found a different point, success!
2517 break;
2518 }
2519 }
2520 return i;
2521}
2522
reed@google.comc1ea60a2012-01-31 15:15:36 +00002523/**
2524 * Starting at index, and moving forward (incrementing), find the xmin and
2525 * xmax of the contiguous points that have the same Y.
2526 */
2527static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2528 int* maxIndexPtr) {
2529 const SkScalar y = pts[index].fY;
2530 SkScalar min = pts[index].fX;
2531 SkScalar max = min;
2532 int minIndex = index;
2533 int maxIndex = index;
2534 for (int i = index + 1; i < n; ++i) {
2535 if (pts[i].fY != y) {
2536 break;
2537 }
2538 SkScalar x = pts[i].fX;
2539 if (x < min) {
2540 min = x;
2541 minIndex = i;
2542 } else if (x > max) {
2543 max = x;
2544 maxIndex = i;
2545 }
2546 }
2547 *maxIndexPtr = maxIndex;
2548 return minIndex;
2549}
2550
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002551static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002552 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002553}
2554
reed@google.comac8543f2012-01-30 20:51:25 +00002555/*
2556 * We loop through all contours, and keep the computed cross-product of the
2557 * contour that contained the global y-max. If we just look at the first
2558 * contour, we may find one that is wound the opposite way (correctly) since
2559 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2560 * that is outer most (or at least has the global y-max) before we can consider
2561 * its cross product.
2562 */
reed@google.com69a99432012-01-10 18:00:10 +00002563bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002564 if (kUnknown_Direction != fDirection) {
2565 *dir = static_cast<Direction>(fDirection);
2566 return true;
2567 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002568
2569 // don't want to pay the cost for computing this if it
2570 // is unknown, so we don't call isConvex()
2571 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2572 SkASSERT(kUnknown_Direction == fDirection);
2573 *dir = static_cast<Direction>(fDirection);
2574 return false;
2575 }
reed@google.com69a99432012-01-10 18:00:10 +00002576
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002577 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002578
reed@google.comac8543f2012-01-30 20:51:25 +00002579 // initialize with our logical y-min
2580 SkScalar ymax = this->getBounds().fTop;
2581 SkScalar ymaxCross = 0;
2582
reed@google.com69a99432012-01-10 18:00:10 +00002583 for (; !iter.done(); iter.next()) {
2584 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002585 if (n < 3) {
2586 continue;
2587 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002588
reed@google.comcabaf1d2012-01-11 21:03:05 +00002589 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002590 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002591 int index = find_max_y(pts, n);
2592 if (pts[index].fY < ymax) {
2593 continue;
2594 }
2595
2596 // If there is more than 1 distinct point at the y-max, we take the
2597 // x-min and x-max of them and just subtract to compute the dir.
2598 if (pts[(index + 1) % n].fY == pts[index].fY) {
2599 int maxIndex;
2600 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2601 if (minIndex == maxIndex) {
2602 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002603 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002604 SkASSERT(pts[minIndex].fY == pts[index].fY);
2605 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2606 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2607 // we just subtract the indices, and let that auto-convert to
2608 // SkScalar, since we just want - or + to signal the direction.
2609 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002610 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002611 TRY_CROSSPROD:
2612 // Find a next and prev index to use for the cross-product test,
2613 // but we try to find pts that form non-zero vectors from pts[index]
2614 //
2615 // Its possible that we can't find two non-degenerate vectors, so
2616 // we have to guard our search (e.g. all the pts could be in the
2617 // same place).
2618
2619 // we pass n - 1 instead of -1 so we don't foul up % operator by
2620 // passing it a negative LH argument.
2621 int prev = find_diff_pt(pts, index, n, n - 1);
2622 if (prev == index) {
2623 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002624 continue;
2625 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002626 int next = find_diff_pt(pts, index, n, 1);
2627 SkASSERT(next != index);
2628 cross = cross_prod(pts[prev], pts[index], pts[next]);
2629 // if we get a zero and the points are horizontal, then we look at the spread in
2630 // x-direction. We really should continue to walk away from the degeneracy until
2631 // there is a divergence.
2632 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2633 // construct the subtract so we get the correct Direction below
2634 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002635 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002636 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002637
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002638 if (cross) {
2639 // record our best guess so far
2640 ymax = pts[index].fY;
2641 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002642 }
2643 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002644 if (ymaxCross) {
2645 crossToDir(ymaxCross, dir);
2646 fDirection = *dir;
2647 return true;
2648 } else {
2649 return false;
2650 }
reed@google.comac8543f2012-01-30 20:51:25 +00002651}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002652
2653///////////////////////////////////////////////////////////////////////////////
2654
2655static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2656 SkScalar D, SkScalar t) {
2657 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2658}
2659
2660static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2661 SkScalar t) {
2662 SkScalar A = c3 + 3*(c1 - c2) - c0;
2663 SkScalar B = 3*(c2 - c1 - c1 + c0);
2664 SkScalar C = 3*(c1 - c0);
2665 SkScalar D = c0;
2666 return eval_cubic_coeff(A, B, C, D, t);
2667}
2668
2669/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2670 t value such that cubic(t) = target
2671 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002672static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002673 SkScalar target, SkScalar* t) {
2674 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2675 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002676
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002677 SkScalar D = c0 - target;
2678 SkScalar A = c3 + 3*(c1 - c2) - c0;
2679 SkScalar B = 3*(c2 - c1 - c1 + c0);
2680 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002681
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002682 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2683 SkScalar minT = 0;
2684 SkScalar maxT = SK_Scalar1;
2685 SkScalar mid;
2686 int i;
2687 for (i = 0; i < 16; i++) {
2688 mid = SkScalarAve(minT, maxT);
2689 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2690 if (delta < 0) {
2691 minT = mid;
2692 delta = -delta;
2693 } else {
2694 maxT = mid;
2695 }
2696 if (delta < TOLERANCE) {
2697 break;
2698 }
2699 }
2700 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002701}
2702
2703template <size_t N> static void find_minmax(const SkPoint pts[],
2704 SkScalar* minPtr, SkScalar* maxPtr) {
2705 SkScalar min, max;
2706 min = max = pts[0].fX;
2707 for (size_t i = 1; i < N; ++i) {
2708 min = SkMinScalar(min, pts[i].fX);
2709 max = SkMaxScalar(max, pts[i].fX);
2710 }
2711 *minPtr = min;
2712 *maxPtr = max;
2713}
2714
2715static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2716 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002717
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002718 int dir = 1;
2719 if (pts[0].fY > pts[3].fY) {
2720 storage[0] = pts[3];
2721 storage[1] = pts[2];
2722 storage[2] = pts[1];
2723 storage[3] = pts[0];
2724 pts = storage;
2725 dir = -1;
2726 }
2727 if (y < pts[0].fY || y >= pts[3].fY) {
2728 return 0;
2729 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731 // quickreject or quickaccept
2732 SkScalar min, max;
2733 find_minmax<4>(pts, &min, &max);
2734 if (x < min) {
2735 return 0;
2736 }
2737 if (x > max) {
2738 return dir;
2739 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002740
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002741 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002742 SkScalar t;
2743 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2744 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 +00002745 return xt < x ? dir : 0;
2746}
2747
2748static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2749 SkPoint dst[10];
2750 int n = SkChopCubicAtYExtrema(pts, dst);
2751 int w = 0;
2752 for (int i = 0; i <= n; ++i) {
2753 w += winding_mono_cubic(&dst[i * 3], x, y);
2754 }
2755 return w;
2756}
2757
2758static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2759 SkScalar y0 = pts[0].fY;
2760 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 int dir = 1;
2763 if (y0 > y2) {
2764 SkTSwap(y0, y2);
2765 dir = -1;
2766 }
2767 if (y < y0 || y >= y2) {
2768 return 0;
2769 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771 // bounds check on X (not required. is it faster?)
2772#if 0
2773 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2774 return 0;
2775 }
2776#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 SkScalar roots[2];
2779 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2780 2 * (pts[1].fY - pts[0].fY),
2781 pts[0].fY - y,
2782 roots);
2783 SkASSERT(n <= 1);
2784 SkScalar xt;
2785 if (0 == n) {
2786 SkScalar mid = SkScalarAve(y0, y2);
2787 // Need [0] and [2] if dir == 1
2788 // and [2] and [0] if dir == -1
2789 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2790 } else {
2791 SkScalar t = roots[0];
2792 SkScalar C = pts[0].fX;
2793 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2794 SkScalar B = 2 * (pts[1].fX - C);
2795 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2796 }
2797 return xt < x ? dir : 0;
2798}
2799
2800static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2801 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2802 if (y0 == y1) {
2803 return true;
2804 }
2805 if (y0 < y1) {
2806 return y1 <= y2;
2807 } else {
2808 return y1 >= y2;
2809 }
2810}
2811
2812static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2813 SkPoint dst[5];
2814 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2817 n = SkChopQuadAtYExtrema(pts, dst);
2818 pts = dst;
2819 }
2820 int w = winding_mono_quad(pts, x, y);
2821 if (n > 0) {
2822 w += winding_mono_quad(&pts[2], x, y);
2823 }
2824 return w;
2825}
2826
2827static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2828 SkScalar x0 = pts[0].fX;
2829 SkScalar y0 = pts[0].fY;
2830 SkScalar x1 = pts[1].fX;
2831 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002834
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 int dir = 1;
2836 if (y0 > y1) {
2837 SkTSwap(y0, y1);
2838 dir = -1;
2839 }
2840 if (y < y0 || y >= y1) {
2841 return 0;
2842 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002843
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2845 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 if (SkScalarSignAsInt(cross) == dir) {
2848 dir = 0;
2849 }
2850 return dir;
2851}
2852
reed@google.com4db592c2013-10-30 17:39:43 +00002853static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2854 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2855}
2856
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857bool SkPath::contains(SkScalar x, SkScalar y) const {
2858 bool isInverse = this->isInverseFillType();
2859 if (this->isEmpty()) {
2860 return isInverse;
2861 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
reed@google.com4db592c2013-10-30 17:39:43 +00002863 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 return isInverse;
2865 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 SkPath::Iter iter(*this, true);
2868 bool done = false;
2869 int w = 0;
2870 do {
2871 SkPoint pts[4];
2872 switch (iter.next(pts, false)) {
2873 case SkPath::kMove_Verb:
2874 case SkPath::kClose_Verb:
2875 break;
2876 case SkPath::kLine_Verb:
2877 w += winding_line(pts, x, y);
2878 break;
2879 case SkPath::kQuad_Verb:
2880 w += winding_quad(pts, x, y);
2881 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002882 case SkPath::kConic_Verb:
2883 SkASSERT(0);
2884 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 case SkPath::kCubic_Verb:
2886 w += winding_cubic(pts, x, y);
2887 break;
2888 case SkPath::kDone_Verb:
2889 done = true;
2890 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002891 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002893
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 switch (this->getFillType()) {
2895 case SkPath::kEvenOdd_FillType:
2896 case SkPath::kInverseEvenOdd_FillType:
2897 w &= 1;
2898 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002899 default:
2900 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901 }
2902 return SkToBool(w);
2903}