blob: ea93963a65989e5470dad0ed0ee3b897935b5fc3 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
41 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
42 }
43
44 ~SkAutoDisableDirectionCheck() {
45 fPath->fDirection = fSaved;
46 }
47
48private:
49 SkPath* fPath;
50 SkPath::Direction fSaved;
51};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000125SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000126 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000127#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000128 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129#endif
130{
131 this->resetFields();
132}
133
134void SkPath::resetFields() {
135 //fPathRef is assumed to have been emptied by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000138 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
141 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000147#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000148 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151}
152
153SkPath::~SkPath() {
154 SkDEBUGCODE(this->validate();)
155}
156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath& SkPath::operator=(const SkPath& that) {
158 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159
bungeman@google.coma5809a32013-06-21 15:13:34 +0000160 if (this != &that) {
161 fPathRef.reset(SkRef(that.fPathRef.get()));
162 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000163#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000164 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000165#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 }
167 SkDEBUGCODE(this->validate();)
168 return *this;
169}
170
bungeman@google.coma5809a32013-06-21 15:13:34 +0000171void SkPath::copyFields(const SkPath& that) {
172 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174 fConvexity = that.fConvexity;
175 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176}
177
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000178bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000179 // note: don't need to look at isConvex or bounds, since just comparing the
180 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000181 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000182 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000183}
184
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185void SkPath::swap(SkPath& that) {
186 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 if (this != &that) {
189 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
192 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000193#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000194 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
195#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 }
197}
198
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000199static inline bool check_edge_against_rect(const SkPoint& p0,
200 const SkPoint& p1,
201 const SkRect& rect,
202 SkPath::Direction dir) {
203 const SkPoint* edgeBegin;
204 SkVector v;
205 if (SkPath::kCW_Direction == dir) {
206 v = p1 - p0;
207 edgeBegin = &p0;
208 } else {
209 v = p0 - p1;
210 edgeBegin = &p1;
211 }
212 if (v.fX || v.fY) {
213 // check the cross product of v with the vec from edgeBegin to each rect corner
214 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
215 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
216 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
217 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
218 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
219 return false;
220 }
221 }
222 return true;
223}
224
225bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
226 // This only handles non-degenerate convex paths currently.
227 if (kConvex_Convexity != this->getConvexity()) {
228 return false;
229 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000230
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000231 Direction direction;
232 if (!this->cheapComputeDirection(&direction)) {
233 return false;
234 }
235
236 SkPoint firstPt;
237 SkPoint prevPt;
238 RawIter iter(*this);
239 SkPath::Verb verb;
240 SkPoint pts[4];
241 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000242 SkDEBUGCODE(int segmentCount = 0;)
243 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000244
245 while ((verb = iter.next(pts)) != kDone_Verb) {
246 int nextPt = -1;
247 switch (verb) {
248 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000249 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 SkDEBUGCODE(++moveCnt);
251 firstPt = prevPt = pts[0];
252 break;
253 case kLine_Verb:
254 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(moveCnt && !closeCount);
256 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 break;
258 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000259 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000260 SkASSERT(moveCnt && !closeCount);
261 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 nextPt = 2;
263 break;
264 case kCubic_Verb:
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 nextPt = 3;
268 break;
269 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000270 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000271 break;
272 default:
273 SkDEBUGFAIL("unknown verb");
274 }
275 if (-1 != nextPt) {
276 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
277 return false;
278 }
279 prevPt = pts[nextPt];
280 }
281 }
282
283 return check_edge_against_rect(prevPt, firstPt, rect, direction);
284}
285
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000286uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000287 uint32_t genID = fPathRef->genID();
288#ifdef SK_BUILD_FOR_ANDROID
289 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
290 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
291#endif
292 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000293}
djsollen@google.come63793a2012-03-21 15:39:03 +0000294
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000295#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000296const SkPath* SkPath::getSourcePath() const {
297 return fSourcePath;
298}
299
300void SkPath::setSourcePath(const SkPath* path) {
301 fSourcePath = path;
302}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000303#endif
304
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305void SkPath::reset() {
306 SkDEBUGCODE(this->validate();)
307
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000308 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000309 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312void SkPath::rewind() {
313 SkDEBUGCODE(this->validate();)
314
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000315 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000316 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317}
318
reed@google.com7e6c4d12012-05-10 14:05:43 +0000319bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000320 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000321
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000322 if (2 == verbCount) {
323 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
324 if (kLine_Verb == fPathRef->atVerb(1)) {
325 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000326 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000327 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000328 line[0] = pts[0];
329 line[1] = pts[1];
330 }
331 return true;
332 }
333 }
334 return false;
335}
336
caryclark@google.comf1316942011-07-26 19:54:45 +0000337/*
338 Determines if path is a rect by keeping track of changes in direction
339 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000340
caryclark@google.comf1316942011-07-26 19:54:45 +0000341 The direction is computed such that:
342 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000343 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000344 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000345 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347A rectangle cycles up/right/down/left or up/left/down/right.
348
349The test fails if:
350 The path is closed, and followed by a line.
351 A second move creates a new endpoint.
352 A diagonal line is parsed.
353 There's more than four changes of direction.
354 There's a discontinuity on the line (e.g., a move in the middle)
355 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000356 The path contains a quadratic or cubic.
357 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000358 *The rectangle doesn't complete a cycle.
359 *The final point isn't equal to the first point.
360
361 *These last two conditions we relax if we have a 3-edge path that would
362 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364It's OK if the path has:
365 Several colinear line segments composing a rectangle side.
366 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000367
caryclark@google.comf1316942011-07-26 19:54:45 +0000368The direction takes advantage of the corners found since opposite sides
369must travel in opposite directions.
370
371FIXME: Allow colinear quads and cubics to be treated like lines.
372FIXME: If the API passes fill-only, return true if the filled stroke
373 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000374
375 first,last,next direction state-machine:
376 0x1 is set if the segment is horizontal
377 0x2 is set if the segment is moving to the right or down
378 thus:
379 two directions are opposites iff (dirA ^ dirB) == 0x2
380 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000383static int rect_make_dir(SkScalar dx, SkScalar dy) {
384 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
385}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
387 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 int corners = 0;
389 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000390 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000391 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000392 first.set(0, 0);
393 last.set(0, 0);
394 int firstDirection = 0;
395 int lastDirection = 0;
396 int nextDirection = 0;
397 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000399 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000400 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
401 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000402 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000403 savePts = pts;
404 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 autoClose = true;
406 case kLine_Verb: {
407 SkScalar left = last.fX;
408 SkScalar top = last.fY;
409 SkScalar right = pts->fX;
410 SkScalar bottom = pts->fY;
411 ++pts;
412 if (left != right && top != bottom) {
413 return false; // diagonal
414 }
415 if (left == right && top == bottom) {
416 break; // single point on side OK
417 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000418 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 if (0 == corners) {
420 firstDirection = nextDirection;
421 first = last;
422 last = pts[-1];
423 corners = 1;
424 closedOrMoved = false;
425 break;
426 }
427 if (closedOrMoved) {
428 return false; // closed followed by a line
429 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000430 if (autoClose && nextDirection == firstDirection) {
431 break; // colinear with first
432 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000433 closedOrMoved = autoClose;
434 if (lastDirection != nextDirection) {
435 if (++corners > 4) {
436 return false; // too many direction changes
437 }
438 }
439 last = pts[-1];
440 if (lastDirection == nextDirection) {
441 break; // colinear segment
442 }
443 // Possible values for corners are 2, 3, and 4.
444 // When corners == 3, nextDirection opposes firstDirection.
445 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000446 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
448 if ((directionCycle ^ turn) != nextDirection) {
449 return false; // direction didn't follow cycle
450 }
451 break;
452 }
453 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000454 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 case kCubic_Verb:
456 return false; // quadratic, cubic not allowed
457 case kMove_Verb:
458 last = *pts++;
459 closedOrMoved = true;
460 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000461 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000462 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000463 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000465 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 lastDirection = nextDirection;
467 }
468 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000469 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000470 if (!result) {
471 // check if we are just an incomplete rectangle, in which case we can
472 // return true, but not claim to be closed.
473 // e.g.
474 // 3 sided rectangle
475 // 4 sided but the last edge is not long enough to reach the start
476 //
477 SkScalar closeX = first.x() - last.x();
478 SkScalar closeY = first.y() - last.y();
479 if (closeX && closeY) {
480 return false; // we're diagonal, abort (can we ever reach this?)
481 }
482 int closeDirection = rect_make_dir(closeX, closeY);
483 // make sure the close-segment doesn't double-back on itself
484 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
485 result = true;
486 autoClose = false; // we are not closed
487 }
488 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000489 if (savePts) {
490 *ptsPtr = savePts;
491 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000492 if (result && isClosed) {
493 *isClosed = autoClose;
494 }
495 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000496 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000497 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000498 return result;
499}
500
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000501SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
502 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000503 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
504 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000505 bool isClosed = false;
506 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
507}
508
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000509bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 SkDEBUGCODE(this->validate();)
511 int currVerb = 0;
512 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000513 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
514 if (result && rect) {
515 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000517 return result;
518}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000519
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000520bool SkPath::isRect(bool* isClosed, Direction* direction) const {
521 SkDEBUGCODE(this->validate();)
522 int currVerb = 0;
523 const SkPoint* pts = fPathRef->points();
524 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000525}
526
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000527bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000528 SkDEBUGCODE(this->validate();)
529 int currVerb = 0;
530 const SkPoint* pts = fPathRef->points();
531 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000532 Direction testDirs[2];
533 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 return false;
535 }
536 const SkPoint* last = pts;
537 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000539 testRects[0].set(first, SkToS32(last - first));
540 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000541 if (testRects[0].contains(testRects[1])) {
542 if (rects) {
543 rects[0] = testRects[0];
544 rects[1] = testRects[1];
545 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000546 if (dirs) {
547 dirs[0] = testDirs[0];
548 dirs[1] = testDirs[1];
549 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 return true;
551 }
552 if (testRects[1].contains(testRects[0])) {
553 if (rects) {
554 rects[0] = testRects[1];
555 rects[1] = testRects[0];
556 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000557 if (dirs) {
558 dirs[0] = testDirs[1];
559 dirs[1] = testDirs[0];
560 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return true;
562 }
563 }
564 return false;
565}
566
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000567int SkPath::countPoints() const {
568 return fPathRef->countPoints();
569}
570
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000571int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000572 SkDEBUGCODE(this->validate();)
573
574 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000575 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576 int count = SkMin32(max, fPathRef->countPoints());
577 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
578 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579}
580
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000581SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
583 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000584 }
585 return SkPoint::Make(0, 0);
586}
587
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588int SkPath::countVerbs() const {
589 return fPathRef->countVerbs();
590}
591
592static inline void copy_verbs_reverse(uint8_t* inorderDst,
593 const uint8_t* reversedSrc,
594 int count) {
595 for (int i = 0; i < count; ++i) {
596 inorderDst[i] = reversedSrc[~i];
597 }
598}
599
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000600int SkPath::getVerbs(uint8_t dst[], int max) const {
601 SkDEBUGCODE(this->validate();)
602
603 SkASSERT(max >= 0);
604 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000605 int count = SkMin32(max, fPathRef->countVerbs());
606 copy_verbs_reverse(dst, fPathRef->verbs(), count);
607 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000608}
609
reed@google.com294dd7b2011-10-11 11:58:32 +0000610bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 SkDEBUGCODE(this->validate();)
612
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000614 if (count > 0) {
615 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000616 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (lastPt) {
621 lastPt->set(0, 0);
622 }
623 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624}
625
626void SkPath::setLastPt(SkScalar x, SkScalar y) {
627 SkDEBUGCODE(this->validate();)
628
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000629 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 if (count == 0) {
631 this->moveTo(x, y);
632 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 SkPathRef::Editor ed(&fPathRef);
634 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 }
636}
637
reed@google.com04863fa2011-05-15 04:08:24 +0000638void SkPath::setConvexity(Convexity c) {
639 if (fConvexity != c) {
640 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000641 }
642}
643
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644//////////////////////////////////////////////////////////////////////////////
645// Construction methods
646
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000647#define DIRTY_AFTER_EDIT \
648 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000649 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000650 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000651 } while (0)
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653void SkPath::incReserve(U16CPU inc) {
654 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 SkDEBUGCODE(this->validate();)
657}
658
659void SkPath::moveTo(SkScalar x, SkScalar y) {
660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665}
666
667void SkPath::rMoveTo(SkScalar x, SkScalar y) {
668 SkPoint pt;
669 this->getLastPt(&pt);
670 this->moveTo(pt.fX + x, pt.fY + y);
671}
672
673void SkPath::lineTo(SkScalar x, SkScalar y) {
674 SkDEBUGCODE(this->validate();)
675
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.comf66cf722014-02-10 13:51:32 +0000677 ed.injectMoveToIfNeeded();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000678 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679
reed@google.comb54455e2011-05-16 14:16:04 +0000680 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681}
682
683void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000684 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 SkPoint pt;
686 this->getLastPt(&pt);
687 this->lineTo(pt.fX + x, pt.fY + y);
688}
689
690void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
691 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.comf66cf722014-02-10 13:51:32 +0000694 ed.injectMoveToIfNeeded();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 pts[0].set(x1, y1);
697 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000698
reed@google.comb54455e2011-05-16 14:16:04 +0000699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000703 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 SkPoint pt;
705 this->getLastPt(&pt);
706 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
707}
708
reed@google.com277c3f82013-05-31 15:17:50 +0000709void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
710 SkScalar w) {
711 // check for <= 0 or NaN with this test
712 if (!(w > 0)) {
713 this->lineTo(x2, y2);
714 } else if (!SkScalarIsFinite(w)) {
715 this->lineTo(x1, y1);
716 this->lineTo(x2, y2);
717 } else if (SK_Scalar1 == w) {
718 this->quadTo(x1, y1, x2, y2);
719 } else {
720 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000721
reed@google.com277c3f82013-05-31 15:17:50 +0000722 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.comf66cf722014-02-10 13:51:32 +0000723 ed.injectMoveToIfNeeded();
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000724 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000725 pts[0].set(x1, y1);
726 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000727
reed@google.com277c3f82013-05-31 15:17:50 +0000728 DIRTY_AFTER_EDIT;
729 }
730}
731
732void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
733 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000734 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
738}
739
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
741 SkScalar x3, SkScalar y3) {
742 SkDEBUGCODE(this->validate();)
743
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000744 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.comf66cf722014-02-10 13:51:32 +0000745 ed.injectMoveToIfNeeded();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000746 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
749 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750
reed@google.comb54455e2011-05-16 14:16:04 +0000751 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752}
753
754void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
755 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000756 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757 SkPoint pt;
758 this->getLastPt(&pt);
759 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
760 pt.fX + x3, pt.fY + y3);
761}
762
763void SkPath::close() {
764 SkDEBUGCODE(this->validate();)
765
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000766 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000768 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 case kLine_Verb:
770 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000771 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000773 case kMove_Verb: {
774 SkPathRef::Editor ed(&fPathRef);
775 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 }
reed@google.com277c3f82013-05-31 15:17:50 +0000778 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000779 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000780 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000781 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000782 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000783 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 }
785 }
786}
787
788///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000789
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000790static void assert_known_direction(int dir) {
791 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
792}
793
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794void SkPath::addRect(const SkRect& rect, Direction dir) {
795 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
796}
797
798void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
799 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000800 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000801 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
802 SkAutoDisableDirectionCheck addc(this);
803
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
805
806 this->incReserve(5);
807
808 this->moveTo(left, top);
809 if (dir == kCCW_Direction) {
810 this->lineTo(left, bottom);
811 this->lineTo(right, bottom);
812 this->lineTo(right, top);
813 } else {
814 this->lineTo(right, top);
815 this->lineTo(right, bottom);
816 this->lineTo(left, bottom);
817 }
818 this->close();
819}
820
reed@google.com744faba2012-05-29 19:54:52 +0000821void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
822 SkDEBUGCODE(this->validate();)
823 if (count <= 0) {
824 return;
825 }
826
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000827 // +close makes room for the extra kClose_Verb
828 SkPathRef::Editor ed(&fPathRef, count+close, count);
829
830 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000831 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000832 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
833 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000834 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000835
reed@google.com744faba2012-05-29 19:54:52 +0000836 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000837 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000838 }
839
reed@google.com744faba2012-05-29 19:54:52 +0000840 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000842}
843
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000844#include "SkGeometry.h"
845
846static int build_arc_points(const SkRect& oval, SkScalar startAngle,
847 SkScalar sweepAngle,
848 SkPoint pts[kSkBuildQuadArcStorage]) {
849
850 if (0 == sweepAngle &&
851 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
852 // Chrome uses this path to move into and out of ovals. If not
853 // treated as a special case the moves can distort the oval's
854 // bounding box (and break the circle special case).
855 pts[0].set(oval.fRight, oval.centerY());
856 return 1;
857 } else if (0 == oval.width() && 0 == oval.height()) {
858 // Chrome will sometimes create 0 radius round rects. Having degenerate
859 // quad segments in the path prevents the path from being recognized as
860 // a rect.
861 // TODO: optimizing the case where only one of width or height is zero
862 // should also be considered. This case, however, doesn't seem to be
863 // as common as the single point case.
864 pts[0].set(oval.fRight, oval.fTop);
865 return 1;
866 }
867
868 SkVector start, stop;
869
870 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
871 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
872 &stop.fX);
873
874 /* If the sweep angle is nearly (but less than) 360, then due to precision
875 loss in radians-conversion and/or sin/cos, we may end up with coincident
876 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
877 of drawing a nearly complete circle (good).
878 e.g. canvas.drawArc(0, 359.99, ...)
879 -vs- canvas.drawArc(0, 359.9, ...)
880 We try to detect this edge case, and tweak the stop vector
881 */
882 if (start == stop) {
883 SkScalar sw = SkScalarAbs(sweepAngle);
884 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
885 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
886 // make a guess at a tiny angle (in radians) to tweak by
887 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
888 // not sure how much will be enough, so we use a loop
889 do {
890 stopRad -= deltaRad;
891 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
892 } while (start == stop);
893 }
894 }
895
896 SkMatrix matrix;
897
898 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
899 matrix.postTranslate(oval.centerX(), oval.centerY());
900
901 return SkBuildQuadArc(start, stop,
902 sweepAngle > 0 ? kCW_SkRotationDirection :
903 kCCW_SkRotationDirection,
904 &matrix, pts);
905}
906
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000907void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000909 SkRRect rrect;
910 rrect.setRectRadii(rect, (const SkVector*) radii);
911 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912}
913
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000914/* The inline clockwise and counterclockwise round rect quad approximations
915 make it easier to see the symmetry patterns used by add corner quads.
916Clockwise corner value
917 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
918 path->quadTo(rect.fLeft, rect.fTop + offPtY,
919 rect.fLeft + midPtX, rect.fTop + midPtY);
920 path->quadTo(rect.fLeft + offPtX, rect.fTop,
921 rect.fLeft + rx, rect.fTop);
922
923 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
924 path->quadTo(rect.fRight - offPtX, rect.fTop,
925 rect.fRight - midPtX, rect.fTop + midPtY);
926 path->quadTo(rect.fRight, rect.fTop + offPtY,
927 rect.fRight, rect.fTop + ry);
928
929 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
930 path->quadTo(rect.fRight, rect.fBottom - offPtY,
931 rect.fRight - midPtX, rect.fBottom - midPtY);
932 path->quadTo(rect.fRight - offPtX, rect.fBottom,
933 rect.fRight - rx, rect.fBottom);
934
935 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
936 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
937 rect.fLeft + midPtX, rect.fBottom - midPtY);
938 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
939 rect.fLeft, rect.fBottom - ry);
940
941Counterclockwise
942 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
943 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
944 rect.fLeft + midPtX, rect.fBottom - midPtY);
945 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
946 rect.fLeft + rx, rect.fBottom);
947
948 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
949 path->quadTo(rect.fRight - offPtX, rect.fBottom,
950 rect.fRight - midPtX, rect.fBottom - midPtY);
951 path->quadTo(rect.fRight, rect.fBottom - offPtY,
952 rect.fRight, rect.fBottom - ry);
953
954 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
955 path->quadTo(rect.fRight, rect.fTop + offPtY,
956 rect.fRight - midPtX, rect.fTop + midPtY);
957 path->quadTo(rect.fRight - offPtX, rect.fTop,
958 rect.fRight - rx, rect.fTop);
959
960 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
961 path->quadTo(rect.fLeft + offPtX, rect.fTop,
962 rect.fLeft + midPtX, rect.fTop + midPtY);
963 path->quadTo(rect.fLeft, rect.fTop + offPtY,
964 rect.fLeft, rect.fTop + ry);
965*/
966static void add_corner_quads(SkPath* path, const SkRRect& rrect,
967 SkRRect::Corner corner, SkPath::Direction dir) {
968 const SkRect& rect = rrect.rect();
969 const SkVector& radii = rrect.radii(corner);
970 SkScalar rx = radii.fX;
971 SkScalar ry = radii.fY;
972 // The mid point of the quadratic arc approximation is half way between the two
973 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +0000974 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
975 SkScalar midPtX = rx * mid;
976 SkScalar midPtY = ry * mid;
977 const SkScalar control = 1 - SK_ScalarTanPIOver8;
978 SkScalar offPtX = rx * control;
979 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000980 static const int kCornerPts = 5;
981 SkScalar xOff[kCornerPts];
982 SkScalar yOff[kCornerPts];
983
984 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
985 SkASSERT(dir == SkPath::kCCW_Direction
986 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
987 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
988 xOff[0] = xOff[1] = 0;
989 xOff[2] = midPtX;
990 xOff[3] = offPtX;
991 xOff[4] = rx;
992 yOff[0] = ry;
993 yOff[1] = offPtY;
994 yOff[2] = midPtY;
995 yOff[3] = yOff[4] = 0;
996 } else {
997 xOff[0] = rx;
998 xOff[1] = offPtX;
999 xOff[2] = midPtX;
1000 xOff[3] = xOff[4] = 0;
1001 yOff[0] = yOff[1] = 0;
1002 yOff[2] = midPtY;
1003 yOff[3] = offPtY;
1004 yOff[4] = ry;
1005 }
1006 if ((corner - 1) & 2) {
1007 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1008 for (int i = 0; i < kCornerPts; ++i) {
1009 xOff[i] = rect.fLeft + xOff[i];
1010 }
1011 } else {
1012 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1013 for (int i = 0; i < kCornerPts; ++i) {
1014 xOff[i] = rect.fRight - xOff[i];
1015 }
1016 }
1017 if (corner < SkRRect::kLowerRight_Corner) {
1018 for (int i = 0; i < kCornerPts; ++i) {
1019 yOff[i] = rect.fTop + yOff[i];
1020 }
1021 } else {
1022 for (int i = 0; i < kCornerPts; ++i) {
1023 yOff[i] = rect.fBottom - yOff[i];
1024 }
1025 }
1026
1027 SkPoint lastPt;
1028 SkAssertResult(path->getLastPt(&lastPt));
1029 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1030 path->lineTo(xOff[0], yOff[0]);
1031 }
1032 if (rx || ry) {
1033 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1034 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1035 } else {
1036 path->lineTo(xOff[2], yOff[2]);
1037 path->lineTo(xOff[4], yOff[4]);
1038 }
1039}
1040
reed@google.com4ed0fb72012-12-12 20:48:18 +00001041void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001042 assert_known_direction(dir);
1043
1044 if (rrect.isEmpty()) {
1045 return;
1046 }
1047
reed@google.com4ed0fb72012-12-12 20:48:18 +00001048 const SkRect& bounds = rrect.getBounds();
1049
1050 if (rrect.isRect()) {
1051 this->addRect(bounds, dir);
1052 } else if (rrect.isOval()) {
1053 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001054#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001055 } else if (rrect.isSimple()) {
1056 const SkVector& rad = rrect.getSimpleRadii();
1057 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001058#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001059 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001060 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001061
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001062 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001063 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001064
1065 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001066 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001067 this->moveTo(bounds.fLeft,
1068 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1069 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1070 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1071 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1072 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001073 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001074 this->moveTo(bounds.fLeft,
1075 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1076 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1077 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1078 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1079 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001080 }
1081 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001082 }
1083}
1084
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001085bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001086 int count = fPathRef->countVerbs();
1087 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1088 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001089 if (*verbs == kLine_Verb ||
1090 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001091 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001092 *verbs == kCubic_Verb) {
1093 return false;
1094 }
1095 ++verbs;
1096 }
1097 return true;
1098}
1099
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001100#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001101#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001102#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001103
1104void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1105 Direction dir) {
1106 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001107
humper@google.com75e3ca12013-04-08 21:44:11 +00001108 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001109 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001110 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001111 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001112 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1113 return;
1114 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001115
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001116#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001117 SkScalar w = rect.width();
1118 SkScalar halfW = SkScalarHalf(w);
1119 SkScalar h = rect.height();
1120 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001121
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001122 if (halfW <= 0 || halfH <= 0) {
1123 return;
1124 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001125
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001126 bool skip_hori = rx >= halfW;
1127 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001128
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001129 if (skip_hori && skip_vert) {
1130 this->addOval(rect, dir);
1131 return;
1132 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001133
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001134 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001135
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001136 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001137 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001138
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001139 if (skip_hori) {
1140 rx = halfW;
1141 } else if (skip_vert) {
1142 ry = halfH;
1143 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001144 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1145 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001146
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001148 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001149 if (dir == kCCW_Direction) {
1150 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001151 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001152 }
1153 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1154 rect.fLeft, rect.fTop + ry - sy,
1155 rect.fLeft, rect.fTop + ry); // top-left
1156 if (!skip_vert) {
1157 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1158 }
1159 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1160 rect.fLeft + rx - sx, rect.fBottom,
1161 rect.fLeft + rx, rect.fBottom); // bot-left
1162 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001163 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001164 }
1165 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1166 rect.fRight, rect.fBottom - ry + sy,
1167 rect.fRight, rect.fBottom - ry); // bot-right
1168 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001169 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001170 }
1171 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1172 rect.fRight - rx + sx, rect.fTop,
1173 rect.fRight - rx, rect.fTop); // top-right
1174 } else {
1175 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1176 rect.fRight, rect.fTop + ry - sy,
1177 rect.fRight, rect.fTop + ry); // top-right
1178 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001179 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001180 }
1181 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1182 rect.fRight - rx + sx, rect.fBottom,
1183 rect.fRight - rx, rect.fBottom); // bot-right
1184 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001185 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001186 }
1187 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1188 rect.fLeft, rect.fBottom - ry + sy,
1189 rect.fLeft, rect.fBottom - ry); // bot-left
1190 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001191 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001192 }
1193 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1194 rect.fLeft + rx - sx, rect.fTop,
1195 rect.fLeft + rx, rect.fTop); // top-left
1196 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 }
1199 }
1200 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001201#else
1202 SkRRect rrect;
1203 rrect.setRectXY(rect, rx, ry);
1204 this->addRRect(rrect, dir);
1205#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001206}
1207
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001209 assert_known_direction(dir);
1210
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001211 /* If addOval() is called after previous moveTo(),
1212 this path is still marked as an oval. This is used to
1213 fit into WebKit's calling sequences.
1214 We can't simply check isEmpty() in this case, as additional
1215 moveTo() would mark the path non empty.
1216 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001217 bool isOval = hasOnlyMoveTos();
1218 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001219 fDirection = dir;
1220 } else {
1221 fDirection = kUnknown_Direction;
1222 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001224 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001225
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226 SkAutoPathBoundsUpdate apbu(this, oval);
1227
1228 SkScalar cx = oval.centerX();
1229 SkScalar cy = oval.centerY();
1230 SkScalar rx = SkScalarHalf(oval.width());
1231 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1234 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1235 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1236 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1237
1238 /*
1239 To handle imprecision in computing the center and radii, we revert to
1240 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1241 to ensure that we don't exceed the oval's bounds *ever*, since we want
1242 to use oval for our fast-bounds, rather than have to recompute it.
1243 */
1244 const SkScalar L = oval.fLeft; // cx - rx
1245 const SkScalar T = oval.fTop; // cy - ry
1246 const SkScalar R = oval.fRight; // cx + rx
1247 const SkScalar B = oval.fBottom; // cy + ry
1248
1249 this->incReserve(17); // 8 quads + close
1250 this->moveTo(R, cy);
1251 if (dir == kCCW_Direction) {
1252 this->quadTo( R, cy - sy, cx + mx, cy - my);
1253 this->quadTo(cx + sx, T, cx , T);
1254 this->quadTo(cx - sx, T, cx - mx, cy - my);
1255 this->quadTo( L, cy - sy, L, cy );
1256 this->quadTo( L, cy + sy, cx - mx, cy + my);
1257 this->quadTo(cx - sx, B, cx , B);
1258 this->quadTo(cx + sx, B, cx + mx, cy + my);
1259 this->quadTo( R, cy + sy, R, cy );
1260 } else {
1261 this->quadTo( R, cy + sy, cx + mx, cy + my);
1262 this->quadTo(cx + sx, B, cx , B);
1263 this->quadTo(cx - sx, B, cx - mx, cy + my);
1264 this->quadTo( L, cy + sy, L, cy );
1265 this->quadTo( L, cy - sy, cx - mx, cy - my);
1266 this->quadTo(cx - sx, T, cx , T);
1267 this->quadTo(cx + sx, T, cx + mx, cy - my);
1268 this->quadTo( R, cy - sy, R, cy );
1269 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271
robertphillips@google.com466310d2013-12-03 16:43:54 +00001272 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001273
robertphillips@google.com466310d2013-12-03 16:43:54 +00001274 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001275}
1276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1278 if (r > 0) {
1279 SkRect rect;
1280 rect.set(x - r, y - r, x + r, y + r);
1281 this->addOval(rect, dir);
1282 }
1283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1286 bool forceMoveTo) {
1287 if (oval.width() < 0 || oval.height() < 0) {
1288 return;
1289 }
1290
1291 SkPoint pts[kSkBuildQuadArcStorage];
1292 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1293 SkASSERT((count & 1) == 1);
1294
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001295 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 forceMoveTo = true;
1297 }
1298 this->incReserve(count);
1299 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1300 for (int i = 1; i < count; i += 2) {
1301 this->quadTo(pts[i], pts[i+1]);
1302 }
1303}
1304
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001305void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 if (oval.isEmpty() || 0 == sweepAngle) {
1307 return;
1308 }
1309
1310 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1311
1312 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1313 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1314 return;
1315 }
1316
1317 SkPoint pts[kSkBuildQuadArcStorage];
1318 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1319
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001320 SkDEBUGCODE(this->validate();)
1321 SkASSERT(count & 1);
1322
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001323 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1324
1325 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1326 if (count > 1) {
1327 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1328 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001329 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001330
1331 DIRTY_AFTER_EDIT;
1332 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333}
1334
1335/*
1336 Need to handle the case when the angle is sharp, and our computed end-points
1337 for the arc go behind pt1 and/or p2...
1338*/
1339void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1340 SkScalar radius) {
1341 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001342
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343 // need to know our prev pt so we can construct tangent vectors
1344 {
1345 SkPoint start;
1346 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001347 // Handle degenerate cases by adding a line to the first point and
1348 // bailing out.
1349 if ((x1 == start.fX && y1 == start.fY) ||
1350 (x1 == x2 && y1 == y2) ||
1351 radius == 0) {
1352 this->lineTo(x1, y1);
1353 return;
1354 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355 before.setNormalize(x1 - start.fX, y1 - start.fY);
1356 after.setNormalize(x2 - x1, y2 - y1);
1357 }
reed@google.comabf15c12011-01-18 20:35:51 +00001358
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 SkScalar cosh = SkPoint::DotProduct(before, after);
1360 SkScalar sinh = SkPoint::CrossProduct(before, after);
1361
1362 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001363 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 return;
1365 }
reed@google.comabf15c12011-01-18 20:35:51 +00001366
reed@android.com8a1c16f2008-12-17 15:59:43 +00001367 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1368 if (dist < 0) {
1369 dist = -dist;
1370 }
1371
1372 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1373 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1374 SkRotationDirection arcDir;
1375
1376 // now turn before/after into normals
1377 if (sinh > 0) {
1378 before.rotateCCW();
1379 after.rotateCCW();
1380 arcDir = kCW_SkRotationDirection;
1381 } else {
1382 before.rotateCW();
1383 after.rotateCW();
1384 arcDir = kCCW_SkRotationDirection;
1385 }
1386
1387 SkMatrix matrix;
1388 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001389
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390 matrix.setScale(radius, radius);
1391 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1392 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001393
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001395
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 this->incReserve(count);
1397 // [xx,yy] == pts[0]
1398 this->lineTo(xx, yy);
1399 for (int i = 1; i < count; i += 2) {
1400 this->quadTo(pts[i], pts[i+1]);
1401 }
1402}
1403
1404///////////////////////////////////////////////////////////////////////////////
1405
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001406void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkMatrix matrix;
1408
1409 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001410 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411}
1412
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001413void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001414 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001416 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417 SkPoint pts[4];
1418 Verb verb;
1419
1420 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001421 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 while ((verb = iter.next(pts)) != kDone_Verb) {
1423 switch (verb) {
1424 case kMove_Verb:
1425 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001426 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1427 injectMoveToIfNeeded(); // In case last contour is closed
1428 this->lineTo(pts[0]);
1429 } else {
1430 this->moveTo(pts[0]);
1431 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 break;
1433 case kLine_Verb:
1434 proc(matrix, &pts[1], &pts[1], 1);
1435 this->lineTo(pts[1]);
1436 break;
1437 case kQuad_Verb:
1438 proc(matrix, &pts[1], &pts[1], 2);
1439 this->quadTo(pts[1], pts[2]);
1440 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001441 case kConic_Verb:
1442 proc(matrix, &pts[1], &pts[1], 2);
1443 this->conicTo(pts[1], pts[2], iter.conicWeight());
1444 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 case kCubic_Verb:
1446 proc(matrix, &pts[1], &pts[1], 3);
1447 this->cubicTo(pts[1], pts[2], pts[3]);
1448 break;
1449 case kClose_Verb:
1450 this->close();
1451 break;
1452 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001453 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001455 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 }
1457}
1458
1459///////////////////////////////////////////////////////////////////////////////
1460
reed@google.com277c3f82013-05-31 15:17:50 +00001461static int pts_in_verb(unsigned verb) {
1462 static const uint8_t gPtsInVerb[] = {
1463 1, // kMove
1464 1, // kLine
1465 2, // kQuad
1466 2, // kConic
1467 3, // kCubic
1468 0, // kClose
1469 0 // kDone
1470 };
1471
1472 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1473 return gPtsInVerb[verb];
1474}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476// ignore the last point of the 1st contour
1477void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001478 int i, vcount = path.fPathRef->countVerbs();
1479 // exit early if the path is empty, or just has a moveTo.
1480 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 return;
1482 }
1483
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001486 const uint8_t* verbs = path.fPathRef->verbs();
1487 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001488 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001490 SkASSERT(verbs[~0] == kMove_Verb);
1491 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001492 unsigned v = verbs[~i];
1493 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 if (n == 0) {
1495 break;
1496 }
1497 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001498 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499 }
1500
1501 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001502 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 case kLine_Verb:
1504 this->lineTo(pts[-1].fX, pts[-1].fY);
1505 break;
1506 case kQuad_Verb:
1507 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1508 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001509 case kConic_Verb:
1510 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1511 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 case kCubic_Verb:
1513 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1514 pts[-3].fX, pts[-3].fY);
1515 break;
1516 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001517 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 break;
1519 }
reed@google.com277c3f82013-05-31 15:17:50 +00001520 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 }
1522}
1523
reed@google.com63d73742012-01-10 15:33:12 +00001524void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001526
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 const SkPoint* pts = src.fPathRef->pointsEnd();
1528 // we will iterator through src's verbs backwards
1529 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1530 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001531 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001532
1533 bool needMove = true;
1534 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001535 while (verbs < verbsEnd) {
1536 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001537 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001538
1539 if (needMove) {
1540 --pts;
1541 this->moveTo(pts->fX, pts->fY);
1542 needMove = false;
1543 }
1544 pts -= n;
1545 switch (v) {
1546 case kMove_Verb:
1547 if (needClose) {
1548 this->close();
1549 needClose = false;
1550 }
1551 needMove = true;
1552 pts += 1; // so we see the point in "if (needMove)" above
1553 break;
1554 case kLine_Verb:
1555 this->lineTo(pts[0]);
1556 break;
1557 case kQuad_Verb:
1558 this->quadTo(pts[1], pts[0]);
1559 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001560 case kConic_Verb:
1561 this->conicTo(pts[1], pts[0], *--conicWeights);
1562 break;
reed@google.com63d73742012-01-10 15:33:12 +00001563 case kCubic_Verb:
1564 this->cubicTo(pts[2], pts[1], pts[0]);
1565 break;
1566 case kClose_Verb:
1567 needClose = true;
1568 break;
1569 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001570 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001571 }
1572 }
1573}
1574
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575///////////////////////////////////////////////////////////////////////////////
1576
1577void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1578 SkMatrix matrix;
1579
1580 matrix.setTranslate(dx, dy);
1581 this->transform(matrix, dst);
1582}
1583
1584#include "SkGeometry.h"
1585
1586static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1587 int level = 2) {
1588 if (--level >= 0) {
1589 SkPoint tmp[5];
1590
1591 SkChopQuadAtHalf(pts, tmp);
1592 subdivide_quad_to(path, &tmp[0], level);
1593 subdivide_quad_to(path, &tmp[2], level);
1594 } else {
1595 path->quadTo(pts[1], pts[2]);
1596 }
1597}
1598
1599static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1600 int level = 2) {
1601 if (--level >= 0) {
1602 SkPoint tmp[7];
1603
1604 SkChopCubicAtHalf(pts, tmp);
1605 subdivide_cubic_to(path, &tmp[0], level);
1606 subdivide_cubic_to(path, &tmp[3], level);
1607 } else {
1608 path->cubicTo(pts[1], pts[2], pts[3]);
1609 }
1610}
1611
1612void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1613 SkDEBUGCODE(this->validate();)
1614 if (dst == NULL) {
1615 dst = (SkPath*)this;
1616 }
1617
tomhudson@google.com8d430182011-06-06 19:11:19 +00001618 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 SkPath tmp;
1620 tmp.fFillType = fFillType;
1621
1622 SkPath::Iter iter(*this, false);
1623 SkPoint pts[4];
1624 SkPath::Verb verb;
1625
reed@google.com4a3b7142012-05-16 17:16:46 +00001626 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 switch (verb) {
1628 case kMove_Verb:
1629 tmp.moveTo(pts[0]);
1630 break;
1631 case kLine_Verb:
1632 tmp.lineTo(pts[1]);
1633 break;
1634 case kQuad_Verb:
1635 subdivide_quad_to(&tmp, pts);
1636 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001637 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001638 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001639 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1640 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 case kCubic_Verb:
1642 subdivide_cubic_to(&tmp, pts);
1643 break;
1644 case kClose_Verb:
1645 tmp.close();
1646 break;
1647 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001648 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
1650 }
1651 }
1652
1653 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001654 SkPathRef::Editor ed(&dst->fPathRef);
1655 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001656 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001658 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1659
reed@android.com8a1c16f2008-12-17 15:59:43 +00001660 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001662 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001664
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001665 if (kUnknown_Direction == fDirection) {
1666 dst->fDirection = kUnknown_Direction;
1667 } else {
1668 SkScalar det2x2 =
1669 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1670 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1671 if (det2x2 < 0) {
1672 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1673 } else if (det2x2 > 0) {
1674 dst->fDirection = fDirection;
1675 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001676 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001677 dst->fDirection = kUnknown_Direction;
1678 }
1679 }
1680
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 SkDEBUGCODE(dst->validate();)
1682 }
1683}
1684
reed@android.com8a1c16f2008-12-17 15:59:43 +00001685///////////////////////////////////////////////////////////////////////////////
1686///////////////////////////////////////////////////////////////////////////////
1687
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001688enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001689 kEmptyContour_SegmentState, // The current contour is empty. We may be
1690 // starting processing or we may have just
1691 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001692 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1693 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1694 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695};
1696
1697SkPath::Iter::Iter() {
1698#ifdef SK_DEBUG
1699 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001700 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001702 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001703 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704#endif
1705 // need to init enough to make next() harmlessly return kDone_Verb
1706 fVerbs = NULL;
1707 fVerbStop = NULL;
1708 fNeedClose = false;
1709}
1710
1711SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1712 this->setPath(path, forceClose);
1713}
1714
1715void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001716 fPts = path.fPathRef->points();
1717 fVerbs = path.fPathRef->verbs();
1718 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001719 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001720 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001721 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 fForceClose = SkToU8(forceClose);
1723 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001724 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725}
1726
1727bool SkPath::Iter::isClosedContour() const {
1728 if (fVerbs == NULL || fVerbs == fVerbStop) {
1729 return false;
1730 }
1731 if (fForceClose) {
1732 return true;
1733 }
1734
1735 const uint8_t* verbs = fVerbs;
1736 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001737
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001738 if (kMove_Verb == *(verbs - 1)) {
1739 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 }
1741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 while (verbs > stop) {
1743 // verbs points one beyond the current verb, decrement first.
1744 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 if (kMove_Verb == v) {
1746 break;
1747 }
1748 if (kClose_Verb == v) {
1749 return true;
1750 }
1751 }
1752 return false;
1753}
1754
1755SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001756 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001758 // A special case: if both points are NaN, SkPoint::operation== returns
1759 // false, but the iterator expects that they are treated as the same.
1760 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001761 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1762 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001763 return kClose_Verb;
1764 }
1765
reed@google.com9e25dbf2012-05-15 17:05:38 +00001766 pts[0] = fLastPt;
1767 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 fLastPt = fMoveTo;
1769 fCloseLine = true;
1770 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001771 } else {
1772 pts[0] = fMoveTo;
1773 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775}
1776
reed@google.com9e25dbf2012-05-15 17:05:38 +00001777const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001778 if (fSegmentState == kAfterMove_SegmentState) {
1779 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001780 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001781 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1784 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001785 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787}
1788
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001789void SkPath::Iter::consumeDegenerateSegments() {
1790 // We need to step over anything that will not move the current draw point
1791 // forward before the next move is seen
1792 const uint8_t* lastMoveVerb = 0;
1793 const SkPoint* lastMovePt = 0;
1794 SkPoint lastPt = fLastPt;
1795 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001796 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001797 switch (verb) {
1798 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001799 // Keep a record of this most recent move
1800 lastMoveVerb = fVerbs;
1801 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001802 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001803 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 fPts++;
1805 break;
1806
1807 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001808 // A close when we are in a segment is always valid except when it
1809 // follows a move which follows a segment.
1810 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 return;
1812 }
1813 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001814 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 break;
1816
1817 case kLine_Verb:
1818 if (!IsLineDegenerate(lastPt, fPts[0])) {
1819 if (lastMoveVerb) {
1820 fVerbs = lastMoveVerb;
1821 fPts = lastMovePt;
1822 return;
1823 }
1824 return;
1825 }
1826 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fPts++;
1829 break;
1830
reed@google.com277c3f82013-05-31 15:17:50 +00001831 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001832 case kQuad_Verb:
1833 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1834 if (lastMoveVerb) {
1835 fVerbs = lastMoveVerb;
1836 fPts = lastMovePt;
1837 return;
1838 }
1839 return;
1840 }
1841 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001844 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 break;
1846
1847 case kCubic_Verb:
1848 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1849 if (lastMoveVerb) {
1850 fVerbs = lastMoveVerb;
1851 fPts = lastMovePt;
1852 return;
1853 }
1854 return;
1855 }
1856 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001857 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 fPts += 3;
1859 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001860
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001862 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 }
1864 }
1865}
1866
reed@google.com4a3b7142012-05-16 17:16:46 +00001867SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001868 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001869
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 // Close the curve if requested and if there is some curve to close
1872 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 return kLine_Verb;
1875 }
1876 fNeedClose = false;
1877 return kClose_Verb;
1878 }
1879 return kDone_Verb;
1880 }
1881
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 // fVerbs is one beyond the current verb, decrement first
1883 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001884 const SkPoint* SK_RESTRICT srcPts = fPts;
1885 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886
1887 switch (verb) {
1888 case kMove_Verb:
1889 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001890 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 verb = this->autoClose(pts);
1892 if (verb == kClose_Verb) {
1893 fNeedClose = false;
1894 }
1895 return (Verb)verb;
1896 }
1897 if (fVerbs == fVerbStop) { // might be a trailing moveto
1898 return kDone_Verb;
1899 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001900 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001903 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 fNeedClose = fForceClose;
1906 break;
1907 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 pts[0] = this->cons_moveTo();
1909 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 fLastPt = srcPts[0];
1911 fCloseLine = false;
1912 srcPts += 1;
1913 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001914 case kConic_Verb:
1915 fConicWeights += 1;
1916 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 pts[0] = this->cons_moveTo();
1919 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 fLastPt = srcPts[1];
1921 srcPts += 2;
1922 break;
1923 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 pts[0] = this->cons_moveTo();
1925 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 fLastPt = srcPts[2];
1927 srcPts += 3;
1928 break;
1929 case kClose_Verb:
1930 verb = this->autoClose(pts);
1931 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 } else {
1934 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001935 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 break;
1939 }
1940 fPts = srcPts;
1941 return (Verb)verb;
1942}
1943
1944///////////////////////////////////////////////////////////////////////////////
1945
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001946SkPath::RawIter::RawIter() {
1947#ifdef SK_DEBUG
1948 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001949 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001950 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1951#endif
1952 // need to init enough to make next() harmlessly return kDone_Verb
1953 fVerbs = NULL;
1954 fVerbStop = NULL;
1955}
1956
1957SkPath::RawIter::RawIter(const SkPath& path) {
1958 this->setPath(path);
1959}
1960
1961void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001962 fPts = path.fPathRef->points();
1963 fVerbs = path.fPathRef->verbs();
1964 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001965 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001966 fMoveTo.fX = fMoveTo.fY = 0;
1967 fLastPt.fX = fLastPt.fY = 0;
1968}
1969
1970SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001971 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001972 if (fVerbs == fVerbStop) {
1973 return kDone_Verb;
1974 }
1975
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001976 // fVerbs points one beyond next verb so decrement first.
1977 unsigned verb = *(--fVerbs);
1978 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001979
1980 switch (verb) {
1981 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001982 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001983 fMoveTo = srcPts[0];
1984 fLastPt = fMoveTo;
1985 srcPts += 1;
1986 break;
1987 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001988 pts[0] = fLastPt;
1989 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990 fLastPt = srcPts[0];
1991 srcPts += 1;
1992 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001993 case kConic_Verb:
1994 fConicWeights += 1;
1995 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001997 pts[0] = fLastPt;
1998 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001999 fLastPt = srcPts[1];
2000 srcPts += 2;
2001 break;
2002 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002003 pts[0] = fLastPt;
2004 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005 fLastPt = srcPts[2];
2006 srcPts += 3;
2007 break;
2008 case kClose_Verb:
2009 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002010 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002011 break;
2012 }
2013 fPts = srcPts;
2014 return (Verb)verb;
2015}
2016
2017///////////////////////////////////////////////////////////////////////////////
2018
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002020 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021*/
2022
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002023size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 SkDEBUGCODE(this->validate();)
2025
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002026 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002027 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002028 return SkAlign4(byteCount);
2029 }
2030
2031 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002032
robertphillips@google.com466310d2013-12-03 16:43:54 +00002033 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002034 (fFillType << kFillType_SerializationShift) |
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002035 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002036
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002037 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002038
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002039 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002040
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002041 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002042 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043}
2044
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002045size_t SkPath::readFromMemory(const void* storage, size_t length) {
2046 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002047
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002048 int32_t packed;
2049 if (!buffer.readS32(&packed)) {
2050 return 0;
2051 }
2052
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002053 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2054 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002055 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002056 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002057
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002058 size_t sizeRead = 0;
2059 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002060 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002061 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002062 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002063 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002064 } else if (NULL != pathRef) {
2065 // If the buffer is not valid, pathRef should be NULL
2066 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002067 }
2068 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069}
2070
2071///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072
reed@google.com51bbe752013-01-17 22:07:50 +00002073#include "SkString.h"
2074
2075static void append_scalar(SkString* str, SkScalar value) {
2076 SkString tmp;
2077 tmp.printf("%g", value);
2078 if (tmp.contains('.')) {
2079 tmp.appendUnichar('f');
2080 }
2081 str->append(tmp);
2082}
2083
2084static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002085 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002086 str->append(label);
2087 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002088
reed@google.com51bbe752013-01-17 22:07:50 +00002089 const SkScalar* values = &pts[0].fX;
2090 count *= 2;
2091
2092 for (int i = 0; i < count; ++i) {
2093 append_scalar(str, values[i]);
2094 if (i < count - 1) {
2095 str->append(", ");
2096 }
2097 }
reed@google.com277c3f82013-05-31 15:17:50 +00002098 if (conicWeight >= 0) {
2099 str->append(", ");
2100 append_scalar(str, conicWeight);
2101 }
reed@google.com51bbe752013-01-17 22:07:50 +00002102 str->append(");\n");
2103}
2104
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105void SkPath::dump(bool forceClose, const char title[]) const {
2106 Iter iter(*this, forceClose);
2107 SkPoint pts[4];
2108 Verb verb;
2109
2110 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2111 title ? title : "");
2112
reed@google.com51bbe752013-01-17 22:07:50 +00002113 SkString builder;
2114
reed@google.com4a3b7142012-05-16 17:16:46 +00002115 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 switch (verb) {
2117 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002118 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 break;
2120 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002121 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 break;
2123 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002124 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002126 case kConic_Verb:
2127 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2128 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002130 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 break;
2132 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002133 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134 break;
2135 default:
2136 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2137 verb = kDone_Verb; // stop the loop
2138 break;
2139 }
2140 }
reed@google.com51bbe752013-01-17 22:07:50 +00002141 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142}
2143
reed@android.come522ca52009-11-23 20:10:41 +00002144void SkPath::dump() const {
2145 this->dump(false);
2146}
2147
2148#ifdef SK_DEBUG
2149void SkPath::validate() const {
2150 SkASSERT(this != NULL);
2151 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002152
djsollen@google.com077348c2012-10-22 20:23:32 +00002153#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002154 if (!fBoundsIsDirty) {
2155 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002156
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002157 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002158 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002159
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002160 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002161 // if we're empty, fBounds may be empty but translated, so we can't
2162 // necessarily compare to bounds directly
2163 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2164 // be [2, 2, 2, 2]
2165 SkASSERT(bounds.isEmpty());
2166 SkASSERT(fBounds.isEmpty());
2167 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002168 if (bounds.isEmpty()) {
2169 SkASSERT(fBounds.isEmpty());
2170 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002171 if (!fBounds.isEmpty()) {
2172 SkASSERT(fBounds.contains(bounds));
2173 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002174 }
reed@android.come522ca52009-11-23 20:10:41 +00002175 }
2176 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002177#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002178}
djsollen@google.com077348c2012-10-22 20:23:32 +00002179#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002180
reed@google.com04863fa2011-05-15 04:08:24 +00002181///////////////////////////////////////////////////////////////////////////////
2182
reed@google.com0b7b9822011-05-16 12:29:27 +00002183static int sign(SkScalar x) { return x < 0; }
2184#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002185
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002186static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2187 // The error epsilon was empirically derived; worse case round rects
2188 // with a mid point outset by 2x float epsilon in tests had an error
2189 // of 12.
2190 const int epsilon = 16;
2191 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2192 return false;
2193 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002194 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002195 int aBits = SkFloatAs2sCompliment(compA);
2196 int bBits = SkFloatAs2sCompliment(compB);
2197 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002198}
2199
2200// only valid for a single contour
2201struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002202 Convexicator()
2203 : fPtCount(0)
2204 , fConvexity(SkPath::kConvex_Convexity)
2205 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002206 fSign = 0;
2207 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002208 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002209 fCurrPt.set(0, 0);
2210 fVec0.set(0, 0);
2211 fVec1.set(0, 0);
2212 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002213
2214 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002215 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002216 }
2217
2218 SkPath::Convexity getConvexity() const { return fConvexity; }
2219
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002220 /** The direction returned is only valid if the path is determined convex */
2221 SkPath::Direction getDirection() const { return fDirection; }
2222
reed@google.com04863fa2011-05-15 04:08:24 +00002223 void addPt(const SkPoint& pt) {
2224 if (SkPath::kConcave_Convexity == fConvexity) {
2225 return;
2226 }
2227
2228 if (0 == fPtCount) {
2229 fCurrPt = pt;
2230 ++fPtCount;
2231 } else {
2232 SkVector vec = pt - fCurrPt;
2233 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002234 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002235 fCurrPt = pt;
2236 if (++fPtCount == 2) {
2237 fFirstVec = fVec1 = vec;
2238 } else {
2239 SkASSERT(fPtCount > 2);
2240 this->addVec(vec);
2241 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002242
reed@google.com85b6e392011-05-15 20:25:17 +00002243 int sx = sign(vec.fX);
2244 int sy = sign(vec.fY);
2245 fDx += (sx != fSx);
2246 fDy += (sy != fSy);
2247 fSx = sx;
2248 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002249
reed@google.com85b6e392011-05-15 20:25:17 +00002250 if (fDx > 3 || fDy > 3) {
2251 fConvexity = SkPath::kConcave_Convexity;
2252 }
reed@google.com04863fa2011-05-15 04:08:24 +00002253 }
2254 }
2255 }
2256
2257 void close() {
2258 if (fPtCount > 2) {
2259 this->addVec(fFirstVec);
2260 }
2261 }
2262
2263private:
2264 void addVec(const SkVector& vec) {
2265 SkASSERT(vec.fX || vec.fY);
2266 fVec0 = fVec1;
2267 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002268 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2269 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2270 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2271 largest = SkTMax(largest, -smallest);
2272 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002273 if (0 == fSign) {
2274 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275 if (1 == sign) {
2276 fDirection = SkPath::kCW_Direction;
2277 } else if (-1 == sign) {
2278 fDirection = SkPath::kCCW_Direction;
2279 }
reed@google.com04863fa2011-05-15 04:08:24 +00002280 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002281 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002282 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002283 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 }
2285 }
2286 }
2287
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002288 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002289 SkPoint fCurrPt;
2290 SkVector fVec0, fVec1, fFirstVec;
2291 int fPtCount; // non-degenerate points
2292 int fSign;
2293 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002294 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002295 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002296};
2297
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002298SkPath::Convexity SkPath::internalGetConvexity() const {
2299 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002300 SkPoint pts[4];
2301 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002302 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002303
2304 int contourCount = 0;
2305 int count;
2306 Convexicator state;
2307
2308 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2309 switch (verb) {
2310 case kMove_Verb:
2311 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002312 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002313 return kConcave_Convexity;
2314 }
2315 pts[1] = pts[0];
2316 count = 1;
2317 break;
2318 case kLine_Verb: count = 1; break;
2319 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002320 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002321 case kCubic_Verb: count = 3; break;
2322 case kClose_Verb:
2323 state.close();
2324 count = 0;
2325 break;
2326 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002327 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 return kConcave_Convexity;
2330 }
2331
2332 for (int i = 1; i <= count; i++) {
2333 state.addPt(pts[i]);
2334 }
2335 // early exit
2336 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002337 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002338 return kConcave_Convexity;
2339 }
2340 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002341 fConvexity = state.getConvexity();
2342 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2343 fDirection = state.getDirection();
2344 }
2345 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002346}
reed@google.com69a99432012-01-10 18:00:10 +00002347
2348///////////////////////////////////////////////////////////////////////////////
2349
2350class ContourIter {
2351public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002352 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002353
2354 bool done() const { return fDone; }
2355 // if !done() then these may be called
2356 int count() const { return fCurrPtCount; }
2357 const SkPoint* pts() const { return fCurrPt; }
2358 void next();
2359
2360private:
2361 int fCurrPtCount;
2362 const SkPoint* fCurrPt;
2363 const uint8_t* fCurrVerb;
2364 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002365 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002366 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002367 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002368};
2369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002370ContourIter::ContourIter(const SkPathRef& pathRef) {
2371 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002372 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002373 fCurrPt = pathRef.points();
2374 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002375 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002376 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002377 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002378 this->next();
2379}
2380
2381void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002382 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002383 fDone = true;
2384 }
2385 if (fDone) {
2386 return;
2387 }
2388
2389 // skip pts of prev contour
2390 fCurrPt += fCurrPtCount;
2391
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002392 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002393 int ptCount = 1; // moveTo
2394 const uint8_t* verbs = fCurrVerb;
2395
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002396 for (--verbs; verbs > fStopVerbs; --verbs) {
2397 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002398 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002399 goto CONTOUR_END;
2400 case SkPath::kLine_Verb:
2401 ptCount += 1;
2402 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002403 case SkPath::kConic_Verb:
2404 fCurrConicWeight += 1;
2405 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002406 case SkPath::kQuad_Verb:
2407 ptCount += 2;
2408 break;
2409 case SkPath::kCubic_Verb:
2410 ptCount += 3;
2411 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002412 case SkPath::kClose_Verb:
2413 break;
2414 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002415 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002416 break;
2417 }
2418 }
2419CONTOUR_END:
2420 fCurrPtCount = ptCount;
2421 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002422 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002423}
2424
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002425// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002426static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002427 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2428 // We may get 0 when the above subtracts underflow. We expect this to be
2429 // very rare and lazily promote to double.
2430 if (0 == cross) {
2431 double p0x = SkScalarToDouble(p0.fX);
2432 double p0y = SkScalarToDouble(p0.fY);
2433
2434 double p1x = SkScalarToDouble(p1.fX);
2435 double p1y = SkScalarToDouble(p1.fY);
2436
2437 double p2x = SkScalarToDouble(p2.fX);
2438 double p2y = SkScalarToDouble(p2.fY);
2439
2440 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2441 (p1y - p0y) * (p2x - p0x));
2442
2443 }
2444 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002445}
2446
reed@google.comc1ea60a2012-01-31 15:15:36 +00002447// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002448static int find_max_y(const SkPoint pts[], int count) {
2449 SkASSERT(count > 0);
2450 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002451 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002452 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002453 SkScalar y = pts[i].fY;
2454 if (y > max) {
2455 max = y;
2456 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002457 }
2458 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002459 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002460}
2461
reed@google.comcabaf1d2012-01-11 21:03:05 +00002462static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2463 int i = index;
2464 for (;;) {
2465 i = (i + inc) % n;
2466 if (i == index) { // we wrapped around, so abort
2467 break;
2468 }
2469 if (pts[index] != pts[i]) { // found a different point, success!
2470 break;
2471 }
2472 }
2473 return i;
2474}
2475
reed@google.comc1ea60a2012-01-31 15:15:36 +00002476/**
2477 * Starting at index, and moving forward (incrementing), find the xmin and
2478 * xmax of the contiguous points that have the same Y.
2479 */
2480static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2481 int* maxIndexPtr) {
2482 const SkScalar y = pts[index].fY;
2483 SkScalar min = pts[index].fX;
2484 SkScalar max = min;
2485 int minIndex = index;
2486 int maxIndex = index;
2487 for (int i = index + 1; i < n; ++i) {
2488 if (pts[i].fY != y) {
2489 break;
2490 }
2491 SkScalar x = pts[i].fX;
2492 if (x < min) {
2493 min = x;
2494 minIndex = i;
2495 } else if (x > max) {
2496 max = x;
2497 maxIndex = i;
2498 }
2499 }
2500 *maxIndexPtr = maxIndex;
2501 return minIndex;
2502}
2503
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002504static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002505 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002506}
2507
reed@google.comac8543f2012-01-30 20:51:25 +00002508/*
2509 * We loop through all contours, and keep the computed cross-product of the
2510 * contour that contained the global y-max. If we just look at the first
2511 * contour, we may find one that is wound the opposite way (correctly) since
2512 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2513 * that is outer most (or at least has the global y-max) before we can consider
2514 * its cross product.
2515 */
reed@google.com69a99432012-01-10 18:00:10 +00002516bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002517 if (kUnknown_Direction != fDirection) {
2518 *dir = static_cast<Direction>(fDirection);
2519 return true;
2520 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002521
2522 // don't want to pay the cost for computing this if it
2523 // is unknown, so we don't call isConvex()
2524 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2525 SkASSERT(kUnknown_Direction == fDirection);
2526 *dir = static_cast<Direction>(fDirection);
2527 return false;
2528 }
reed@google.com69a99432012-01-10 18:00:10 +00002529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002531
reed@google.comac8543f2012-01-30 20:51:25 +00002532 // initialize with our logical y-min
2533 SkScalar ymax = this->getBounds().fTop;
2534 SkScalar ymaxCross = 0;
2535
reed@google.com69a99432012-01-10 18:00:10 +00002536 for (; !iter.done(); iter.next()) {
2537 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002538 if (n < 3) {
2539 continue;
2540 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002541
reed@google.comcabaf1d2012-01-11 21:03:05 +00002542 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002543 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002544 int index = find_max_y(pts, n);
2545 if (pts[index].fY < ymax) {
2546 continue;
2547 }
2548
2549 // If there is more than 1 distinct point at the y-max, we take the
2550 // x-min and x-max of them and just subtract to compute the dir.
2551 if (pts[(index + 1) % n].fY == pts[index].fY) {
2552 int maxIndex;
2553 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2554 if (minIndex == maxIndex) {
2555 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002556 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002557 SkASSERT(pts[minIndex].fY == pts[index].fY);
2558 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2559 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2560 // we just subtract the indices, and let that auto-convert to
2561 // SkScalar, since we just want - or + to signal the direction.
2562 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002563 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002564 TRY_CROSSPROD:
2565 // Find a next and prev index to use for the cross-product test,
2566 // but we try to find pts that form non-zero vectors from pts[index]
2567 //
2568 // Its possible that we can't find two non-degenerate vectors, so
2569 // we have to guard our search (e.g. all the pts could be in the
2570 // same place).
2571
2572 // we pass n - 1 instead of -1 so we don't foul up % operator by
2573 // passing it a negative LH argument.
2574 int prev = find_diff_pt(pts, index, n, n - 1);
2575 if (prev == index) {
2576 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002577 continue;
2578 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002579 int next = find_diff_pt(pts, index, n, 1);
2580 SkASSERT(next != index);
2581 cross = cross_prod(pts[prev], pts[index], pts[next]);
2582 // if we get a zero and the points are horizontal, then we look at the spread in
2583 // x-direction. We really should continue to walk away from the degeneracy until
2584 // there is a divergence.
2585 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2586 // construct the subtract so we get the correct Direction below
2587 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002588 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002589 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002590
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002591 if (cross) {
2592 // record our best guess so far
2593 ymax = pts[index].fY;
2594 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002595 }
2596 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002597 if (ymaxCross) {
2598 crossToDir(ymaxCross, dir);
2599 fDirection = *dir;
2600 return true;
2601 } else {
2602 return false;
2603 }
reed@google.comac8543f2012-01-30 20:51:25 +00002604}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002605
2606///////////////////////////////////////////////////////////////////////////////
2607
2608static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2609 SkScalar D, SkScalar t) {
2610 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2611}
2612
2613static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2614 SkScalar t) {
2615 SkScalar A = c3 + 3*(c1 - c2) - c0;
2616 SkScalar B = 3*(c2 - c1 - c1 + c0);
2617 SkScalar C = 3*(c1 - c0);
2618 SkScalar D = c0;
2619 return eval_cubic_coeff(A, B, C, D, t);
2620}
2621
2622/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2623 t value such that cubic(t) = target
2624 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002625static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002626 SkScalar target, SkScalar* t) {
2627 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2628 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002629
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002630 SkScalar D = c0 - target;
2631 SkScalar A = c3 + 3*(c1 - c2) - c0;
2632 SkScalar B = 3*(c2 - c1 - c1 + c0);
2633 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002634
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002635 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2636 SkScalar minT = 0;
2637 SkScalar maxT = SK_Scalar1;
2638 SkScalar mid;
2639 int i;
2640 for (i = 0; i < 16; i++) {
2641 mid = SkScalarAve(minT, maxT);
2642 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2643 if (delta < 0) {
2644 minT = mid;
2645 delta = -delta;
2646 } else {
2647 maxT = mid;
2648 }
2649 if (delta < TOLERANCE) {
2650 break;
2651 }
2652 }
2653 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002654}
2655
2656template <size_t N> static void find_minmax(const SkPoint pts[],
2657 SkScalar* minPtr, SkScalar* maxPtr) {
2658 SkScalar min, max;
2659 min = max = pts[0].fX;
2660 for (size_t i = 1; i < N; ++i) {
2661 min = SkMinScalar(min, pts[i].fX);
2662 max = SkMaxScalar(max, pts[i].fX);
2663 }
2664 *minPtr = min;
2665 *maxPtr = max;
2666}
2667
2668static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2669 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002670
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002671 int dir = 1;
2672 if (pts[0].fY > pts[3].fY) {
2673 storage[0] = pts[3];
2674 storage[1] = pts[2];
2675 storage[2] = pts[1];
2676 storage[3] = pts[0];
2677 pts = storage;
2678 dir = -1;
2679 }
2680 if (y < pts[0].fY || y >= pts[3].fY) {
2681 return 0;
2682 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002683
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002684 // quickreject or quickaccept
2685 SkScalar min, max;
2686 find_minmax<4>(pts, &min, &max);
2687 if (x < min) {
2688 return 0;
2689 }
2690 if (x > max) {
2691 return dir;
2692 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002693
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002694 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002695 SkScalar t;
2696 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2697 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 +00002698 return xt < x ? dir : 0;
2699}
2700
2701static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2702 SkPoint dst[10];
2703 int n = SkChopCubicAtYExtrema(pts, dst);
2704 int w = 0;
2705 for (int i = 0; i <= n; ++i) {
2706 w += winding_mono_cubic(&dst[i * 3], x, y);
2707 }
2708 return w;
2709}
2710
2711static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2712 SkScalar y0 = pts[0].fY;
2713 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002714
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002715 int dir = 1;
2716 if (y0 > y2) {
2717 SkTSwap(y0, y2);
2718 dir = -1;
2719 }
2720 if (y < y0 || y >= y2) {
2721 return 0;
2722 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 // bounds check on X (not required. is it faster?)
2725#if 0
2726 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2727 return 0;
2728 }
2729#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731 SkScalar roots[2];
2732 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2733 2 * (pts[1].fY - pts[0].fY),
2734 pts[0].fY - y,
2735 roots);
2736 SkASSERT(n <= 1);
2737 SkScalar xt;
2738 if (0 == n) {
2739 SkScalar mid = SkScalarAve(y0, y2);
2740 // Need [0] and [2] if dir == 1
2741 // and [2] and [0] if dir == -1
2742 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2743 } else {
2744 SkScalar t = roots[0];
2745 SkScalar C = pts[0].fX;
2746 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2747 SkScalar B = 2 * (pts[1].fX - C);
2748 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2749 }
2750 return xt < x ? dir : 0;
2751}
2752
2753static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2754 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2755 if (y0 == y1) {
2756 return true;
2757 }
2758 if (y0 < y1) {
2759 return y1 <= y2;
2760 } else {
2761 return y1 >= y2;
2762 }
2763}
2764
2765static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2766 SkPoint dst[5];
2767 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2770 n = SkChopQuadAtYExtrema(pts, dst);
2771 pts = dst;
2772 }
2773 int w = winding_mono_quad(pts, x, y);
2774 if (n > 0) {
2775 w += winding_mono_quad(&pts[2], x, y);
2776 }
2777 return w;
2778}
2779
2780static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2781 SkScalar x0 = pts[0].fX;
2782 SkScalar y0 = pts[0].fY;
2783 SkScalar x1 = pts[1].fX;
2784 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 int dir = 1;
2789 if (y0 > y1) {
2790 SkTSwap(y0, y1);
2791 dir = -1;
2792 }
2793 if (y < y0 || y >= y1) {
2794 return 0;
2795 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002796
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002797 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2798 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002799
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 if (SkScalarSignAsInt(cross) == dir) {
2801 dir = 0;
2802 }
2803 return dir;
2804}
2805
reed@google.com4db592c2013-10-30 17:39:43 +00002806static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2807 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2808}
2809
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810bool SkPath::contains(SkScalar x, SkScalar y) const {
2811 bool isInverse = this->isInverseFillType();
2812 if (this->isEmpty()) {
2813 return isInverse;
2814 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
reed@google.com4db592c2013-10-30 17:39:43 +00002816 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 return isInverse;
2818 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 SkPath::Iter iter(*this, true);
2821 bool done = false;
2822 int w = 0;
2823 do {
2824 SkPoint pts[4];
2825 switch (iter.next(pts, false)) {
2826 case SkPath::kMove_Verb:
2827 case SkPath::kClose_Verb:
2828 break;
2829 case SkPath::kLine_Verb:
2830 w += winding_line(pts, x, y);
2831 break;
2832 case SkPath::kQuad_Verb:
2833 w += winding_quad(pts, x, y);
2834 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002835 case SkPath::kConic_Verb:
2836 SkASSERT(0);
2837 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 case SkPath::kCubic_Verb:
2839 w += winding_cubic(pts, x, y);
2840 break;
2841 case SkPath::kDone_Verb:
2842 done = true;
2843 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002844 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 switch (this->getFillType()) {
2848 case SkPath::kEvenOdd_FillType:
2849 case SkPath::kInverseEvenOdd_FillType:
2850 w &= 1;
2851 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002852 default:
2853 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002854 }
2855 return SkToBool(w);
2856}