blob: 04c807b64415492aa4b3ee617391a22c2800ea78 [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
reed@google.comd335d1d2012-01-12 18:17:11 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000129 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000131 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132#endif
133{
134 this->resetFields();
135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181}
182
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000183bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000184 // note: don't need to look at isConvex or bounds, since just comparing the
185 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000186 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000187 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190void SkPath::swap(SkPath& that) {
191 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 if (this != &that) {
194 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
196 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
198 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000199#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000200 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
201#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000205static inline bool check_edge_against_rect(const SkPoint& p0,
206 const SkPoint& p1,
207 const SkRect& rect,
208 SkPath::Direction dir) {
209 const SkPoint* edgeBegin;
210 SkVector v;
211 if (SkPath::kCW_Direction == dir) {
212 v = p1 - p0;
213 edgeBegin = &p0;
214 } else {
215 v = p0 - p1;
216 edgeBegin = &p1;
217 }
218 if (v.fX || v.fY) {
219 // check the cross product of v with the vec from edgeBegin to each rect corner
220 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
221 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
222 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
223 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
224 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
225 return false;
226 }
227 }
228 return true;
229}
230
231bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
232 // This only handles non-degenerate convex paths currently.
233 if (kConvex_Convexity != this->getConvexity()) {
234 return false;
235 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237 Direction direction;
238 if (!this->cheapComputeDirection(&direction)) {
239 return false;
240 }
241
242 SkPoint firstPt;
243 SkPoint prevPt;
244 RawIter iter(*this);
245 SkPath::Verb verb;
246 SkPoint pts[4];
247 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000248 SkDEBUGCODE(int segmentCount = 0;)
249 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250
251 while ((verb = iter.next(pts)) != kDone_Verb) {
252 int nextPt = -1;
253 switch (verb) {
254 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000256 SkDEBUGCODE(++moveCnt);
257 firstPt = prevPt = pts[0];
258 break;
259 case kLine_Verb:
260 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 break;
264 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000265 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkASSERT(moveCnt && !closeCount);
267 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 nextPt = 2;
269 break;
270 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkASSERT(moveCnt && !closeCount);
272 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273 nextPt = 3;
274 break;
275 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000276 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 break;
278 default:
279 SkDEBUGFAIL("unknown verb");
280 }
281 if (-1 != nextPt) {
282 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
283 return false;
284 }
285 prevPt = pts[nextPt];
286 }
287 }
288
289 return check_edge_against_rect(prevPt, firstPt, rect, direction);
290}
291
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000292uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000293 uint32_t genID = fPathRef->genID();
294#ifdef SK_BUILD_FOR_ANDROID
295 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
296 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
297#endif
298 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000299}
djsollen@google.come63793a2012-03-21 15:39:03 +0000300
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000302const SkPath* SkPath::getSourcePath() const {
303 return fSourcePath;
304}
305
306void SkPath::setSourcePath(const SkPath* path) {
307 fSourcePath = path;
308}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000309#endif
310
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311void SkPath::reset() {
312 SkDEBUGCODE(this->validate();)
313
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000314 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000315 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316}
317
318void SkPath::rewind() {
319 SkDEBUGCODE(this->validate();)
320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000322 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323}
324
reed@google.com7e6c4d12012-05-10 14:05:43 +0000325bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000326 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000327
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000328 if (2 == verbCount) {
329 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
330 if (kLine_Verb == fPathRef->atVerb(1)) {
331 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000332 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000334 line[0] = pts[0];
335 line[1] = pts[1];
336 }
337 return true;
338 }
339 }
340 return false;
341}
342
caryclark@google.comf1316942011-07-26 19:54:45 +0000343/*
344 Determines if path is a rect by keeping track of changes in direction
345 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347 The direction is computed such that:
348 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000349 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000350 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000351 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000352
caryclark@google.comf1316942011-07-26 19:54:45 +0000353A rectangle cycles up/right/down/left or up/left/down/right.
354
355The test fails if:
356 The path is closed, and followed by a line.
357 A second move creates a new endpoint.
358 A diagonal line is parsed.
359 There's more than four changes of direction.
360 There's a discontinuity on the line (e.g., a move in the middle)
361 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000362 The path contains a quadratic or cubic.
363 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000364 *The rectangle doesn't complete a cycle.
365 *The final point isn't equal to the first point.
366
367 *These last two conditions we relax if we have a 3-edge path that would
368 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370It's OK if the path has:
371 Several colinear line segments composing a rectangle side.
372 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374The direction takes advantage of the corners found since opposite sides
375must travel in opposite directions.
376
377FIXME: Allow colinear quads and cubics to be treated like lines.
378FIXME: If the API passes fill-only, return true if the filled stroke
379 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000380
381 first,last,next direction state-machine:
382 0x1 is set if the segment is horizontal
383 0x2 is set if the segment is moving to the right or down
384 thus:
385 two directions are opposites iff (dirA ^ dirB) == 0x2
386 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000389static int rect_make_dir(SkScalar dx, SkScalar dy) {
390 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
391}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
393 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 int corners = 0;
395 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000396 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000397 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000398 first.set(0, 0);
399 last.set(0, 0);
400 int firstDirection = 0;
401 int lastDirection = 0;
402 int nextDirection = 0;
403 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000405 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
407 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 savePts = pts;
410 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 autoClose = true;
412 case kLine_Verb: {
413 SkScalar left = last.fX;
414 SkScalar top = last.fY;
415 SkScalar right = pts->fX;
416 SkScalar bottom = pts->fY;
417 ++pts;
418 if (left != right && top != bottom) {
419 return false; // diagonal
420 }
421 if (left == right && top == bottom) {
422 break; // single point on side OK
423 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 if (0 == corners) {
426 firstDirection = nextDirection;
427 first = last;
428 last = pts[-1];
429 corners = 1;
430 closedOrMoved = false;
431 break;
432 }
433 if (closedOrMoved) {
434 return false; // closed followed by a line
435 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000436 if (autoClose && nextDirection == firstDirection) {
437 break; // colinear with first
438 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 closedOrMoved = autoClose;
440 if (lastDirection != nextDirection) {
441 if (++corners > 4) {
442 return false; // too many direction changes
443 }
444 }
445 last = pts[-1];
446 if (lastDirection == nextDirection) {
447 break; // colinear segment
448 }
449 // Possible values for corners are 2, 3, and 4.
450 // When corners == 3, nextDirection opposes firstDirection.
451 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
454 if ((directionCycle ^ turn) != nextDirection) {
455 return false; // direction didn't follow cycle
456 }
457 break;
458 }
459 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000460 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 case kCubic_Verb:
462 return false; // quadratic, cubic not allowed
463 case kMove_Verb:
464 last = *pts++;
465 closedOrMoved = true;
466 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000467 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000468 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000469 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 lastDirection = nextDirection;
473 }
474 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000476 if (!result) {
477 // check if we are just an incomplete rectangle, in which case we can
478 // return true, but not claim to be closed.
479 // e.g.
480 // 3 sided rectangle
481 // 4 sided but the last edge is not long enough to reach the start
482 //
483 SkScalar closeX = first.x() - last.x();
484 SkScalar closeY = first.y() - last.y();
485 if (closeX && closeY) {
486 return false; // we're diagonal, abort (can we ever reach this?)
487 }
488 int closeDirection = rect_make_dir(closeX, closeY);
489 // make sure the close-segment doesn't double-back on itself
490 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
491 result = true;
492 autoClose = false; // we are not closed
493 }
494 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000507SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
508 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
509 SK_COMPILE_ASSERT(1 == kStroke_PathAsRect, path_as_rect_mismatch);
510 SK_COMPILE_ASSERT(2 == kFill_PathAsRect, path_as_rect_mismatch);
511 bool isClosed = false;
512 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
513}
514
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000516 SkDEBUGCODE(this->validate();)
517 int currVerb = 0;
518 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000519 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
520 if (result && rect) {
521 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000522 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000523 return result;
524}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000525
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000526bool SkPath::isRect(bool* isClosed, Direction* direction) const {
527 SkDEBUGCODE(this->validate();)
528 int currVerb = 0;
529 const SkPoint* pts = fPathRef->points();
530 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000531}
532
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000533bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 SkDEBUGCODE(this->validate();)
535 int currVerb = 0;
536 const SkPoint* pts = fPathRef->points();
537 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 Direction testDirs[2];
539 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000540 return false;
541 }
542 const SkPoint* last = pts;
543 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[0].set(first, SkToS32(last - first));
546 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 if (testRects[0].contains(testRects[1])) {
548 if (rects) {
549 rects[0] = testRects[0];
550 rects[1] = testRects[1];
551 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000552 if (dirs) {
553 dirs[0] = testDirs[0];
554 dirs[1] = testDirs[1];
555 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 return true;
557 }
558 if (testRects[1].contains(testRects[0])) {
559 if (rects) {
560 rects[0] = testRects[1];
561 rects[1] = testRects[0];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[1];
565 dirs[1] = testDirs[0];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 }
570 return false;
571}
572
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000573int SkPath::countPoints() const {
574 return fPathRef->countPoints();
575}
576
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000577int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 SkDEBUGCODE(this->validate();)
579
580 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000581 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 int count = SkMin32(max, fPathRef->countPoints());
583 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
584 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585}
586
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000587SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
589 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590 }
591 return SkPoint::Make(0, 0);
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countVerbs() const {
595 return fPathRef->countVerbs();
596}
597
598static inline void copy_verbs_reverse(uint8_t* inorderDst,
599 const uint8_t* reversedSrc,
600 int count) {
601 for (int i = 0; i < count; ++i) {
602 inorderDst[i] = reversedSrc[~i];
603 }
604}
605
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606int SkPath::getVerbs(uint8_t dst[], int max) const {
607 SkDEBUGCODE(this->validate();)
608
609 SkASSERT(max >= 0);
610 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = SkMin32(max, fPathRef->countVerbs());
612 copy_verbs_reverse(dst, fPathRef->verbs(), count);
613 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000614}
615
reed@google.com294dd7b2011-10-11 11:58:32 +0000616bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 SkDEBUGCODE(this->validate();)
618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000619 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (count > 0) {
621 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000624 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 if (lastPt) {
627 lastPt->set(0, 0);
628 }
629 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630}
631
632void SkPath::setLastPt(SkScalar x, SkScalar y) {
633 SkDEBUGCODE(this->validate();)
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 if (count == 0) {
637 this->moveTo(x, y);
638 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 SkPathRef::Editor ed(&fPathRef);
640 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 }
642}
643
reed@google.com04863fa2011-05-15 04:08:24 +0000644void SkPath::setConvexity(Convexity c) {
645 if (fConvexity != c) {
646 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000647 }
648}
649
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650//////////////////////////////////////////////////////////////////////////////
651// Construction methods
652
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000653#define DIRTY_AFTER_EDIT \
654 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000655 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000656 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000657 } while (0)
658
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659void SkPath::incReserve(U16CPU inc) {
660 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 SkDEBUGCODE(this->validate();)
663}
664
665void SkPath::moveTo(SkScalar x, SkScalar y) {
666 SkDEBUGCODE(this->validate();)
667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669
reed@google.comd335d1d2012-01-12 18:17:11 +0000670 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000671 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000672
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
676void SkPath::rMoveTo(SkScalar x, SkScalar y) {
677 SkPoint pt;
678 this->getLastPt(&pt);
679 this->moveTo(pt.fX + x, pt.fY + y);
680}
681
reed@google.comd335d1d2012-01-12 18:17:11 +0000682void SkPath::injectMoveToIfNeeded() {
683 if (fLastMoveToIndex < 0) {
684 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000686 x = y = 0;
687 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000689 x = pt.fX;
690 y = pt.fY;
691 }
692 this->moveTo(x, y);
693 }
694}
695
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696void SkPath::lineTo(SkScalar x, SkScalar y) {
697 SkDEBUGCODE(this->validate();)
698
reed@google.comd335d1d2012-01-12 18:17:11 +0000699 this->injectMoveToIfNeeded();
700
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000701 SkPathRef::Editor ed(&fPathRef);
702 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703
reed@google.comb54455e2011-05-16 14:16:04 +0000704 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705}
706
707void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000708 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 SkPoint pt;
710 this->getLastPt(&pt);
711 this->lineTo(pt.fX + x, pt.fY + y);
712}
713
714void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
715 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000716
reed@google.comd335d1d2012-01-12 18:17:11 +0000717 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 SkPathRef::Editor ed(&fPathRef);
720 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721 pts[0].set(x1, y1);
722 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000723
reed@google.comb54455e2011-05-16 14:16:04 +0000724 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725}
726
727void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000728 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729 SkPoint pt;
730 this->getLastPt(&pt);
731 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
732}
733
reed@google.com277c3f82013-05-31 15:17:50 +0000734void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
735 SkScalar w) {
736 // check for <= 0 or NaN with this test
737 if (!(w > 0)) {
738 this->lineTo(x2, y2);
739 } else if (!SkScalarIsFinite(w)) {
740 this->lineTo(x1, y1);
741 this->lineTo(x2, y2);
742 } else if (SK_Scalar1 == w) {
743 this->quadTo(x1, y1, x2, y2);
744 } else {
745 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000746
reed@google.com277c3f82013-05-31 15:17:50 +0000747 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000748
reed@google.com277c3f82013-05-31 15:17:50 +0000749 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000750 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000751 pts[0].set(x1, y1);
752 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000753
reed@google.com277c3f82013-05-31 15:17:50 +0000754 DIRTY_AFTER_EDIT;
755 }
756}
757
758void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
759 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000760 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000761 SkPoint pt;
762 this->getLastPt(&pt);
763 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
764}
765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
767 SkScalar x3, SkScalar y3) {
768 SkDEBUGCODE(this->validate();)
769
reed@google.comd335d1d2012-01-12 18:17:11 +0000770 this->injectMoveToIfNeeded();
771
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000772 SkPathRef::Editor ed(&fPathRef);
773 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 pts[0].set(x1, y1);
775 pts[1].set(x2, y2);
776 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777
reed@google.comb54455e2011-05-16 14:16:04 +0000778 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779}
780
781void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
782 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000783 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 SkPoint pt;
785 this->getLastPt(&pt);
786 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
787 pt.fX + x3, pt.fY + y3);
788}
789
790void SkPath::close() {
791 SkDEBUGCODE(this->validate();)
792
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796 case kLine_Verb:
797 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000798 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000800 case kMove_Verb: {
801 SkPathRef::Editor ed(&fPathRef);
802 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000804 }
reed@google.com277c3f82013-05-31 15:17:50 +0000805 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000806 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000807 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000808 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000809 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000810 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 }
812 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000813
814 // signal that we need a moveTo to follow us (unless we're done)
815#if 0
816 if (fLastMoveToIndex >= 0) {
817 fLastMoveToIndex = ~fLastMoveToIndex;
818 }
819#else
820 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
821#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822}
823
824///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000825
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000826static void assert_known_direction(int dir) {
827 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
828}
829
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830void SkPath::addRect(const SkRect& rect, Direction dir) {
831 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
832}
833
834void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
835 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000836 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000837 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
838 SkAutoDisableDirectionCheck addc(this);
839
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
841
842 this->incReserve(5);
843
844 this->moveTo(left, top);
845 if (dir == kCCW_Direction) {
846 this->lineTo(left, bottom);
847 this->lineTo(right, bottom);
848 this->lineTo(right, top);
849 } else {
850 this->lineTo(right, top);
851 this->lineTo(right, bottom);
852 this->lineTo(left, bottom);
853 }
854 this->close();
855}
856
reed@google.com744faba2012-05-29 19:54:52 +0000857void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
858 SkDEBUGCODE(this->validate();)
859 if (count <= 0) {
860 return;
861 }
862
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000863 fLastMoveToIndex = fPathRef->countPoints();
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000864
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000865 // +close makes room for the extra kClose_Verb
866 SkPathRef::Editor ed(&fPathRef, count+close, count);
867
868 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000869 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000870 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
871 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000872 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000873
reed@google.com744faba2012-05-29 19:54:52 +0000874 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000875 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000876 }
877
reed@google.com744faba2012-05-29 19:54:52 +0000878 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000880}
881
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000882#include "SkGeometry.h"
883
884static int build_arc_points(const SkRect& oval, SkScalar startAngle,
885 SkScalar sweepAngle,
886 SkPoint pts[kSkBuildQuadArcStorage]) {
887
888 if (0 == sweepAngle &&
889 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
890 // Chrome uses this path to move into and out of ovals. If not
891 // treated as a special case the moves can distort the oval's
892 // bounding box (and break the circle special case).
893 pts[0].set(oval.fRight, oval.centerY());
894 return 1;
895 } else if (0 == oval.width() && 0 == oval.height()) {
896 // Chrome will sometimes create 0 radius round rects. Having degenerate
897 // quad segments in the path prevents the path from being recognized as
898 // a rect.
899 // TODO: optimizing the case where only one of width or height is zero
900 // should also be considered. This case, however, doesn't seem to be
901 // as common as the single point case.
902 pts[0].set(oval.fRight, oval.fTop);
903 return 1;
904 }
905
906 SkVector start, stop;
907
908 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
909 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
910 &stop.fX);
911
912 /* If the sweep angle is nearly (but less than) 360, then due to precision
913 loss in radians-conversion and/or sin/cos, we may end up with coincident
914 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
915 of drawing a nearly complete circle (good).
916 e.g. canvas.drawArc(0, 359.99, ...)
917 -vs- canvas.drawArc(0, 359.9, ...)
918 We try to detect this edge case, and tweak the stop vector
919 */
920 if (start == stop) {
921 SkScalar sw = SkScalarAbs(sweepAngle);
922 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
923 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
924 // make a guess at a tiny angle (in radians) to tweak by
925 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
926 // not sure how much will be enough, so we use a loop
927 do {
928 stopRad -= deltaRad;
929 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
930 } while (start == stop);
931 }
932 }
933
934 SkMatrix matrix;
935
936 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
937 matrix.postTranslate(oval.centerX(), oval.centerY());
938
939 return SkBuildQuadArc(start, stop,
940 sweepAngle > 0 ? kCW_SkRotationDirection :
941 kCCW_SkRotationDirection,
942 &matrix, pts);
943}
944
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000945void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000947 SkRRect rrect;
948 rrect.setRectRadii(rect, (const SkVector*) radii);
949 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950}
951
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000952/* The inline clockwise and counterclockwise round rect quad approximations
953 make it easier to see the symmetry patterns used by add corner quads.
954Clockwise corner value
955 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
956 path->quadTo(rect.fLeft, rect.fTop + offPtY,
957 rect.fLeft + midPtX, rect.fTop + midPtY);
958 path->quadTo(rect.fLeft + offPtX, rect.fTop,
959 rect.fLeft + rx, rect.fTop);
960
961 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
962 path->quadTo(rect.fRight - offPtX, rect.fTop,
963 rect.fRight - midPtX, rect.fTop + midPtY);
964 path->quadTo(rect.fRight, rect.fTop + offPtY,
965 rect.fRight, rect.fTop + ry);
966
967 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
968 path->quadTo(rect.fRight, rect.fBottom - offPtY,
969 rect.fRight - midPtX, rect.fBottom - midPtY);
970 path->quadTo(rect.fRight - offPtX, rect.fBottom,
971 rect.fRight - rx, rect.fBottom);
972
973 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
974 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
975 rect.fLeft + midPtX, rect.fBottom - midPtY);
976 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
977 rect.fLeft, rect.fBottom - ry);
978
979Counterclockwise
980 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
981 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
982 rect.fLeft + midPtX, rect.fBottom - midPtY);
983 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
984 rect.fLeft + rx, rect.fBottom);
985
986 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
987 path->quadTo(rect.fRight - offPtX, rect.fBottom,
988 rect.fRight - midPtX, rect.fBottom - midPtY);
989 path->quadTo(rect.fRight, rect.fBottom - offPtY,
990 rect.fRight, rect.fBottom - ry);
991
992 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
993 path->quadTo(rect.fRight, rect.fTop + offPtY,
994 rect.fRight - midPtX, rect.fTop + midPtY);
995 path->quadTo(rect.fRight - offPtX, rect.fTop,
996 rect.fRight - rx, rect.fTop);
997
998 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
999 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1000 rect.fLeft + midPtX, rect.fTop + midPtY);
1001 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1002 rect.fLeft, rect.fTop + ry);
1003*/
1004static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1005 SkRRect::Corner corner, SkPath::Direction dir) {
1006 const SkRect& rect = rrect.rect();
1007 const SkVector& radii = rrect.radii(corner);
1008 SkScalar rx = radii.fX;
1009 SkScalar ry = radii.fY;
1010 // The mid point of the quadratic arc approximation is half way between the two
1011 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001012 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1013 SkScalar midPtX = rx * mid;
1014 SkScalar midPtY = ry * mid;
1015 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1016 SkScalar offPtX = rx * control;
1017 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001018 static const int kCornerPts = 5;
1019 SkScalar xOff[kCornerPts];
1020 SkScalar yOff[kCornerPts];
1021
1022 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1023 SkASSERT(dir == SkPath::kCCW_Direction
1024 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1025 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1026 xOff[0] = xOff[1] = 0;
1027 xOff[2] = midPtX;
1028 xOff[3] = offPtX;
1029 xOff[4] = rx;
1030 yOff[0] = ry;
1031 yOff[1] = offPtY;
1032 yOff[2] = midPtY;
1033 yOff[3] = yOff[4] = 0;
1034 } else {
1035 xOff[0] = rx;
1036 xOff[1] = offPtX;
1037 xOff[2] = midPtX;
1038 xOff[3] = xOff[4] = 0;
1039 yOff[0] = yOff[1] = 0;
1040 yOff[2] = midPtY;
1041 yOff[3] = offPtY;
1042 yOff[4] = ry;
1043 }
1044 if ((corner - 1) & 2) {
1045 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1046 for (int i = 0; i < kCornerPts; ++i) {
1047 xOff[i] = rect.fLeft + xOff[i];
1048 }
1049 } else {
1050 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1051 for (int i = 0; i < kCornerPts; ++i) {
1052 xOff[i] = rect.fRight - xOff[i];
1053 }
1054 }
1055 if (corner < SkRRect::kLowerRight_Corner) {
1056 for (int i = 0; i < kCornerPts; ++i) {
1057 yOff[i] = rect.fTop + yOff[i];
1058 }
1059 } else {
1060 for (int i = 0; i < kCornerPts; ++i) {
1061 yOff[i] = rect.fBottom - yOff[i];
1062 }
1063 }
1064
1065 SkPoint lastPt;
1066 SkAssertResult(path->getLastPt(&lastPt));
1067 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1068 path->lineTo(xOff[0], yOff[0]);
1069 }
1070 if (rx || ry) {
1071 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1072 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1073 } else {
1074 path->lineTo(xOff[2], yOff[2]);
1075 path->lineTo(xOff[4], yOff[4]);
1076 }
1077}
1078
reed@google.com4ed0fb72012-12-12 20:48:18 +00001079void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001080 assert_known_direction(dir);
1081
1082 if (rrect.isEmpty()) {
1083 return;
1084 }
1085
reed@google.com4ed0fb72012-12-12 20:48:18 +00001086 const SkRect& bounds = rrect.getBounds();
1087
1088 if (rrect.isRect()) {
1089 this->addRect(bounds, dir);
1090 } else if (rrect.isOval()) {
1091 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001092#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001093 } else if (rrect.isSimple()) {
1094 const SkVector& rad = rrect.getSimpleRadii();
1095 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001096#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001097 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001098 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001100 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001101 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102
1103 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001104 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001105 this->moveTo(bounds.fLeft,
1106 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1107 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1108 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1109 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1110 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001111 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001112 this->moveTo(bounds.fLeft,
1113 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1114 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1115 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1116 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1117 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001118 }
1119 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001120 }
1121}
1122
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001123bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001124 int count = fPathRef->countVerbs();
1125 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1126 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001127 if (*verbs == kLine_Verb ||
1128 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001129 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001130 *verbs == kCubic_Verb) {
1131 return false;
1132 }
1133 ++verbs;
1134 }
1135 return true;
1136}
1137
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001138#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001139#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001140#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001141
1142void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1143 Direction dir) {
1144 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001145
humper@google.com75e3ca12013-04-08 21:44:11 +00001146 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001147 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001148 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001149 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001150 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1151 return;
1152 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001153
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001154#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001155 SkScalar w = rect.width();
1156 SkScalar halfW = SkScalarHalf(w);
1157 SkScalar h = rect.height();
1158 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001159
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 if (halfW <= 0 || halfH <= 0) {
1161 return;
1162 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001163
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001164 bool skip_hori = rx >= halfW;
1165 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001166
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001167 if (skip_hori && skip_vert) {
1168 this->addOval(rect, dir);
1169 return;
1170 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001171
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001173
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001174 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001175 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001176
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001177 if (skip_hori) {
1178 rx = halfW;
1179 } else if (skip_vert) {
1180 ry = halfH;
1181 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001182 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1183 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001184
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001185 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001186 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001187 if (dir == kCCW_Direction) {
1188 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001189 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001190 }
1191 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1192 rect.fLeft, rect.fTop + ry - sy,
1193 rect.fLeft, rect.fTop + ry); // top-left
1194 if (!skip_vert) {
1195 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1196 }
1197 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1198 rect.fLeft + rx - sx, rect.fBottom,
1199 rect.fLeft + rx, rect.fBottom); // bot-left
1200 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001201 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001202 }
1203 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1204 rect.fRight, rect.fBottom - ry + sy,
1205 rect.fRight, rect.fBottom - ry); // bot-right
1206 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001207 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001208 }
1209 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1210 rect.fRight - rx + sx, rect.fTop,
1211 rect.fRight - rx, rect.fTop); // top-right
1212 } else {
1213 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1214 rect.fRight, rect.fTop + ry - sy,
1215 rect.fRight, rect.fTop + ry); // top-right
1216 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001217 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001218 }
1219 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1220 rect.fRight - rx + sx, rect.fBottom,
1221 rect.fRight - rx, rect.fBottom); // bot-right
1222 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001223 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001224 }
1225 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1226 rect.fLeft, rect.fBottom - ry + sy,
1227 rect.fLeft, rect.fBottom - ry); // bot-left
1228 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001229 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001230 }
1231 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1232 rect.fLeft + rx - sx, rect.fTop,
1233 rect.fLeft + rx, rect.fTop); // top-left
1234 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001235 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001236 }
1237 }
1238 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001239#else
1240 SkRRect rrect;
1241 rrect.setRectXY(rect, rx, ry);
1242 this->addRRect(rrect, dir);
1243#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001244}
1245
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001247 assert_known_direction(dir);
1248
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001249 /* If addOval() is called after previous moveTo(),
1250 this path is still marked as an oval. This is used to
1251 fit into WebKit's calling sequences.
1252 We can't simply check isEmpty() in this case, as additional
1253 moveTo() would mark the path non empty.
1254 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001255 bool isOval = hasOnlyMoveTos();
1256 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 fDirection = dir;
1258 } else {
1259 fDirection = kUnknown_Direction;
1260 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001261
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001262 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001263
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 SkAutoPathBoundsUpdate apbu(this, oval);
1265
1266 SkScalar cx = oval.centerX();
1267 SkScalar cy = oval.centerY();
1268 SkScalar rx = SkScalarHalf(oval.width());
1269 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1272 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1273 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1274 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1275
1276 /*
1277 To handle imprecision in computing the center and radii, we revert to
1278 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1279 to ensure that we don't exceed the oval's bounds *ever*, since we want
1280 to use oval for our fast-bounds, rather than have to recompute it.
1281 */
1282 const SkScalar L = oval.fLeft; // cx - rx
1283 const SkScalar T = oval.fTop; // cy - ry
1284 const SkScalar R = oval.fRight; // cx + rx
1285 const SkScalar B = oval.fBottom; // cy + ry
1286
1287 this->incReserve(17); // 8 quads + close
1288 this->moveTo(R, cy);
1289 if (dir == kCCW_Direction) {
1290 this->quadTo( R, cy - sy, cx + mx, cy - my);
1291 this->quadTo(cx + sx, T, cx , T);
1292 this->quadTo(cx - sx, T, cx - mx, cy - my);
1293 this->quadTo( L, cy - sy, L, cy );
1294 this->quadTo( L, cy + sy, cx - mx, cy + my);
1295 this->quadTo(cx - sx, B, cx , B);
1296 this->quadTo(cx + sx, B, cx + mx, cy + my);
1297 this->quadTo( R, cy + sy, R, cy );
1298 } else {
1299 this->quadTo( R, cy + sy, cx + mx, cy + my);
1300 this->quadTo(cx + sx, B, cx , B);
1301 this->quadTo(cx - sx, B, cx - mx, cy + my);
1302 this->quadTo( L, cy + sy, L, cy );
1303 this->quadTo( L, cy - sy, cx - mx, cy - my);
1304 this->quadTo(cx - sx, T, cx , T);
1305 this->quadTo(cx + sx, T, cx + mx, cy - my);
1306 this->quadTo( R, cy - sy, R, cy );
1307 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309
robertphillips@google.com466310d2013-12-03 16:43:54 +00001310 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001311
robertphillips@google.com466310d2013-12-03 16:43:54 +00001312 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001313}
1314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1316 if (r > 0) {
1317 SkRect rect;
1318 rect.set(x - r, y - r, x + r, y + r);
1319 this->addOval(rect, dir);
1320 }
1321}
1322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1324 bool forceMoveTo) {
1325 if (oval.width() < 0 || oval.height() < 0) {
1326 return;
1327 }
1328
1329 SkPoint pts[kSkBuildQuadArcStorage];
1330 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1331 SkASSERT((count & 1) == 1);
1332
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001333 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 forceMoveTo = true;
1335 }
1336 this->incReserve(count);
1337 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1338 for (int i = 1; i < count; i += 2) {
1339 this->quadTo(pts[i], pts[i+1]);
1340 }
1341}
1342
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001343void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 if (oval.isEmpty() || 0 == sweepAngle) {
1345 return;
1346 }
1347
1348 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1349
1350 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1351 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1352 return;
1353 }
1354
1355 SkPoint pts[kSkBuildQuadArcStorage];
1356 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1357
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001358 SkDEBUGCODE(this->validate();)
1359 SkASSERT(count & 1);
1360
1361 fLastMoveToIndex = fPathRef->countPoints();
1362
1363 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1364
1365 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1366 if (count > 1) {
1367 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1368 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001370
1371 DIRTY_AFTER_EDIT;
1372 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373}
1374
1375/*
1376 Need to handle the case when the angle is sharp, and our computed end-points
1377 for the arc go behind pt1 and/or p2...
1378*/
1379void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1380 SkScalar radius) {
1381 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001382
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 // need to know our prev pt so we can construct tangent vectors
1384 {
1385 SkPoint start;
1386 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001387 // Handle degenerate cases by adding a line to the first point and
1388 // bailing out.
1389 if ((x1 == start.fX && y1 == start.fY) ||
1390 (x1 == x2 && y1 == y2) ||
1391 radius == 0) {
1392 this->lineTo(x1, y1);
1393 return;
1394 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 before.setNormalize(x1 - start.fX, y1 - start.fY);
1396 after.setNormalize(x2 - x1, y2 - y1);
1397 }
reed@google.comabf15c12011-01-18 20:35:51 +00001398
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 SkScalar cosh = SkPoint::DotProduct(before, after);
1400 SkScalar sinh = SkPoint::CrossProduct(before, after);
1401
1402 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001403 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 return;
1405 }
reed@google.comabf15c12011-01-18 20:35:51 +00001406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1408 if (dist < 0) {
1409 dist = -dist;
1410 }
1411
1412 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1413 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1414 SkRotationDirection arcDir;
1415
1416 // now turn before/after into normals
1417 if (sinh > 0) {
1418 before.rotateCCW();
1419 after.rotateCCW();
1420 arcDir = kCW_SkRotationDirection;
1421 } else {
1422 before.rotateCW();
1423 after.rotateCW();
1424 arcDir = kCCW_SkRotationDirection;
1425 }
1426
1427 SkMatrix matrix;
1428 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 matrix.setScale(radius, radius);
1431 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1432 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001433
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001435
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 this->incReserve(count);
1437 // [xx,yy] == pts[0]
1438 this->lineTo(xx, yy);
1439 for (int i = 1; i < count; i += 2) {
1440 this->quadTo(pts[i], pts[i+1]);
1441 }
1442}
1443
1444///////////////////////////////////////////////////////////////////////////////
1445
1446void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1447 SkMatrix matrix;
1448
1449 matrix.setTranslate(dx, dy);
1450 this->addPath(path, matrix);
1451}
1452
1453void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001456 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkPoint pts[4];
1458 Verb verb;
1459
1460 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1461
1462 while ((verb = iter.next(pts)) != kDone_Verb) {
1463 switch (verb) {
1464 case kMove_Verb:
1465 proc(matrix, &pts[0], &pts[0], 1);
1466 this->moveTo(pts[0]);
1467 break;
1468 case kLine_Verb:
1469 proc(matrix, &pts[1], &pts[1], 1);
1470 this->lineTo(pts[1]);
1471 break;
1472 case kQuad_Verb:
1473 proc(matrix, &pts[1], &pts[1], 2);
1474 this->quadTo(pts[1], pts[2]);
1475 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001476 case kConic_Verb:
1477 proc(matrix, &pts[1], &pts[1], 2);
1478 this->conicTo(pts[1], pts[2], iter.conicWeight());
1479 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 case kCubic_Verb:
1481 proc(matrix, &pts[1], &pts[1], 3);
1482 this->cubicTo(pts[1], pts[2], pts[3]);
1483 break;
1484 case kClose_Verb:
1485 this->close();
1486 break;
1487 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001488 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 }
1490 }
1491}
1492
1493///////////////////////////////////////////////////////////////////////////////
1494
reed@google.com277c3f82013-05-31 15:17:50 +00001495static int pts_in_verb(unsigned verb) {
1496 static const uint8_t gPtsInVerb[] = {
1497 1, // kMove
1498 1, // kLine
1499 2, // kQuad
1500 2, // kConic
1501 3, // kCubic
1502 0, // kClose
1503 0 // kDone
1504 };
1505
1506 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1507 return gPtsInVerb[verb];
1508}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510// ignore the last point of the 1st contour
1511void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001512 int i, vcount = path.fPathRef->countVerbs();
1513 // exit early if the path is empty, or just has a moveTo.
1514 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 return;
1516 }
1517
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001520 const uint8_t* verbs = path.fPathRef->verbs();
1521 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001522 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 SkASSERT(verbs[~0] == kMove_Verb);
1525 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001526 unsigned v = verbs[~i];
1527 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 if (n == 0) {
1529 break;
1530 }
1531 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001532 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 }
1534
1535 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001536 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 case kLine_Verb:
1538 this->lineTo(pts[-1].fX, pts[-1].fY);
1539 break;
1540 case kQuad_Verb:
1541 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1542 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001543 case kConic_Verb:
1544 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1545 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 case kCubic_Verb:
1547 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1548 pts[-3].fX, pts[-3].fY);
1549 break;
1550 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001551 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 break;
1553 }
reed@google.com277c3f82013-05-31 15:17:50 +00001554 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 }
1556}
1557
reed@google.com63d73742012-01-10 15:33:12 +00001558void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001559 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001560
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001561 const SkPoint* pts = src.fPathRef->pointsEnd();
1562 // we will iterator through src's verbs backwards
1563 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1564 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001565 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001566
1567 bool needMove = true;
1568 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 while (verbs < verbsEnd) {
1570 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001571 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001572
1573 if (needMove) {
1574 --pts;
1575 this->moveTo(pts->fX, pts->fY);
1576 needMove = false;
1577 }
1578 pts -= n;
1579 switch (v) {
1580 case kMove_Verb:
1581 if (needClose) {
1582 this->close();
1583 needClose = false;
1584 }
1585 needMove = true;
1586 pts += 1; // so we see the point in "if (needMove)" above
1587 break;
1588 case kLine_Verb:
1589 this->lineTo(pts[0]);
1590 break;
1591 case kQuad_Verb:
1592 this->quadTo(pts[1], pts[0]);
1593 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001594 case kConic_Verb:
1595 this->conicTo(pts[1], pts[0], *--conicWeights);
1596 break;
reed@google.com63d73742012-01-10 15:33:12 +00001597 case kCubic_Verb:
1598 this->cubicTo(pts[2], pts[1], pts[0]);
1599 break;
1600 case kClose_Verb:
1601 needClose = true;
1602 break;
1603 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001604 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001605 }
1606 }
1607}
1608
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609///////////////////////////////////////////////////////////////////////////////
1610
1611void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1612 SkMatrix matrix;
1613
1614 matrix.setTranslate(dx, dy);
1615 this->transform(matrix, dst);
1616}
1617
1618#include "SkGeometry.h"
1619
1620static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1621 int level = 2) {
1622 if (--level >= 0) {
1623 SkPoint tmp[5];
1624
1625 SkChopQuadAtHalf(pts, tmp);
1626 subdivide_quad_to(path, &tmp[0], level);
1627 subdivide_quad_to(path, &tmp[2], level);
1628 } else {
1629 path->quadTo(pts[1], pts[2]);
1630 }
1631}
1632
1633static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1634 int level = 2) {
1635 if (--level >= 0) {
1636 SkPoint tmp[7];
1637
1638 SkChopCubicAtHalf(pts, tmp);
1639 subdivide_cubic_to(path, &tmp[0], level);
1640 subdivide_cubic_to(path, &tmp[3], level);
1641 } else {
1642 path->cubicTo(pts[1], pts[2], pts[3]);
1643 }
1644}
1645
1646void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1647 SkDEBUGCODE(this->validate();)
1648 if (dst == NULL) {
1649 dst = (SkPath*)this;
1650 }
1651
tomhudson@google.com8d430182011-06-06 19:11:19 +00001652 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 SkPath tmp;
1654 tmp.fFillType = fFillType;
1655
1656 SkPath::Iter iter(*this, false);
1657 SkPoint pts[4];
1658 SkPath::Verb verb;
1659
reed@google.com4a3b7142012-05-16 17:16:46 +00001660 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 switch (verb) {
1662 case kMove_Verb:
1663 tmp.moveTo(pts[0]);
1664 break;
1665 case kLine_Verb:
1666 tmp.lineTo(pts[1]);
1667 break;
1668 case kQuad_Verb:
1669 subdivide_quad_to(&tmp, pts);
1670 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001671 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001672 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001673 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1674 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 case kCubic_Verb:
1676 subdivide_cubic_to(&tmp, pts);
1677 break;
1678 case kClose_Verb:
1679 tmp.close();
1680 break;
1681 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001682 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 break;
1684 }
1685 }
1686
1687 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 SkPathRef::Editor ed(&dst->fPathRef);
1689 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001690 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001692 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1693
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001696 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001698
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001699 if (kUnknown_Direction == fDirection) {
1700 dst->fDirection = kUnknown_Direction;
1701 } else {
1702 SkScalar det2x2 =
1703 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1704 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1705 if (det2x2 < 0) {
1706 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1707 } else if (det2x2 > 0) {
1708 dst->fDirection = fDirection;
1709 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001710 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001711 dst->fDirection = kUnknown_Direction;
1712 }
1713 }
1714
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 SkDEBUGCODE(dst->validate();)
1716 }
1717}
1718
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719///////////////////////////////////////////////////////////////////////////////
1720///////////////////////////////////////////////////////////////////////////////
1721
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001722enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001723 kEmptyContour_SegmentState, // The current contour is empty. We may be
1724 // starting processing or we may have just
1725 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1727 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1728 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729};
1730
1731SkPath::Iter::Iter() {
1732#ifdef SK_DEBUG
1733 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001734 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001737 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738#endif
1739 // need to init enough to make next() harmlessly return kDone_Verb
1740 fVerbs = NULL;
1741 fVerbStop = NULL;
1742 fNeedClose = false;
1743}
1744
1745SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1746 this->setPath(path, forceClose);
1747}
1748
1749void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001750 fPts = path.fPathRef->points();
1751 fVerbs = path.fPathRef->verbs();
1752 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001753 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001754 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001755 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 fForceClose = SkToU8(forceClose);
1757 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001758 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759}
1760
1761bool SkPath::Iter::isClosedContour() const {
1762 if (fVerbs == NULL || fVerbs == fVerbStop) {
1763 return false;
1764 }
1765 if (fForceClose) {
1766 return true;
1767 }
1768
1769 const uint8_t* verbs = fVerbs;
1770 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001771
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001772 if (kMove_Verb == *(verbs - 1)) {
1773 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 }
1775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001776 while (verbs > stop) {
1777 // verbs points one beyond the current verb, decrement first.
1778 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 if (kMove_Verb == v) {
1780 break;
1781 }
1782 if (kClose_Verb == v) {
1783 return true;
1784 }
1785 }
1786 return false;
1787}
1788
1789SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001792 // A special case: if both points are NaN, SkPoint::operation== returns
1793 // false, but the iterator expects that they are treated as the same.
1794 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001795 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1796 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001797 return kClose_Verb;
1798 }
1799
reed@google.com9e25dbf2012-05-15 17:05:38 +00001800 pts[0] = fLastPt;
1801 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 fLastPt = fMoveTo;
1803 fCloseLine = true;
1804 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001805 } else {
1806 pts[0] = fMoveTo;
1807 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809}
1810
reed@google.com9e25dbf2012-05-15 17:05:38 +00001811const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812 if (fSegmentState == kAfterMove_SegmentState) {
1813 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001815 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1818 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001819 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821}
1822
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823void SkPath::Iter::consumeDegenerateSegments() {
1824 // We need to step over anything that will not move the current draw point
1825 // forward before the next move is seen
1826 const uint8_t* lastMoveVerb = 0;
1827 const SkPoint* lastMovePt = 0;
1828 SkPoint lastPt = fLastPt;
1829 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 switch (verb) {
1832 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 // Keep a record of this most recent move
1834 lastMoveVerb = fVerbs;
1835 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001836 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 fPts++;
1839 break;
1840
1841 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001842 // A close when we are in a segment is always valid except when it
1843 // follows a move which follows a segment.
1844 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 return;
1846 }
1847 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001848 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 break;
1850
1851 case kLine_Verb:
1852 if (!IsLineDegenerate(lastPt, fPts[0])) {
1853 if (lastMoveVerb) {
1854 fVerbs = lastMoveVerb;
1855 fPts = lastMovePt;
1856 return;
1857 }
1858 return;
1859 }
1860 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001861 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 fPts++;
1863 break;
1864
reed@google.com277c3f82013-05-31 15:17:50 +00001865 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 case kQuad_Verb:
1867 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1868 if (lastMoveVerb) {
1869 fVerbs = lastMoveVerb;
1870 fPts = lastMovePt;
1871 return;
1872 }
1873 return;
1874 }
1875 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001876 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001878 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 break;
1880
1881 case kCubic_Verb:
1882 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1883 if (lastMoveVerb) {
1884 fVerbs = lastMoveVerb;
1885 fPts = lastMovePt;
1886 return;
1887 }
1888 return;
1889 }
1890 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 fPts += 3;
1893 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001894
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001896 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 }
1898 }
1899}
1900
reed@google.com4a3b7142012-05-16 17:16:46 +00001901SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 // Close the curve if requested and if there is some curve to close
1906 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 return kLine_Verb;
1909 }
1910 fNeedClose = false;
1911 return kClose_Verb;
1912 }
1913 return kDone_Verb;
1914 }
1915
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 // fVerbs is one beyond the current verb, decrement first
1917 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 const SkPoint* SK_RESTRICT srcPts = fPts;
1919 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920
1921 switch (verb) {
1922 case kMove_Verb:
1923 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 verb = this->autoClose(pts);
1926 if (verb == kClose_Verb) {
1927 fNeedClose = false;
1928 }
1929 return (Verb)verb;
1930 }
1931 if (fVerbs == fVerbStop) { // might be a trailing moveto
1932 return kDone_Verb;
1933 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001934 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001935 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001937 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fNeedClose = fForceClose;
1940 break;
1941 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001942 pts[0] = this->cons_moveTo();
1943 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 fLastPt = srcPts[0];
1945 fCloseLine = false;
1946 srcPts += 1;
1947 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001948 case kConic_Verb:
1949 fConicWeights += 1;
1950 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001952 pts[0] = this->cons_moveTo();
1953 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 fLastPt = srcPts[1];
1955 srcPts += 2;
1956 break;
1957 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001958 pts[0] = this->cons_moveTo();
1959 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 fLastPt = srcPts[2];
1961 srcPts += 3;
1962 break;
1963 case kClose_Verb:
1964 verb = this->autoClose(pts);
1965 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001966 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 } else {
1968 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001969 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 break;
1973 }
1974 fPts = srcPts;
1975 return (Verb)verb;
1976}
1977
1978///////////////////////////////////////////////////////////////////////////////
1979
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001980SkPath::RawIter::RawIter() {
1981#ifdef SK_DEBUG
1982 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001983 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001984 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1985#endif
1986 // need to init enough to make next() harmlessly return kDone_Verb
1987 fVerbs = NULL;
1988 fVerbStop = NULL;
1989}
1990
1991SkPath::RawIter::RawIter(const SkPath& path) {
1992 this->setPath(path);
1993}
1994
1995void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001996 fPts = path.fPathRef->points();
1997 fVerbs = path.fPathRef->verbs();
1998 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001999 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002000 fMoveTo.fX = fMoveTo.fY = 0;
2001 fLastPt.fX = fLastPt.fY = 0;
2002}
2003
2004SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002005 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006 if (fVerbs == fVerbStop) {
2007 return kDone_Verb;
2008 }
2009
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002010 // fVerbs points one beyond next verb so decrement first.
2011 unsigned verb = *(--fVerbs);
2012 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002013
2014 switch (verb) {
2015 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002016 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002017 fMoveTo = srcPts[0];
2018 fLastPt = fMoveTo;
2019 srcPts += 1;
2020 break;
2021 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002022 pts[0] = fLastPt;
2023 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002024 fLastPt = srcPts[0];
2025 srcPts += 1;
2026 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002027 case kConic_Verb:
2028 fConicWeights += 1;
2029 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002031 pts[0] = fLastPt;
2032 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002033 fLastPt = srcPts[1];
2034 srcPts += 2;
2035 break;
2036 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002037 pts[0] = fLastPt;
2038 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002039 fLastPt = srcPts[2];
2040 srcPts += 3;
2041 break;
2042 case kClose_Verb:
2043 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002044 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045 break;
2046 }
2047 fPts = srcPts;
2048 return (Verb)verb;
2049}
2050
2051///////////////////////////////////////////////////////////////////////////////
2052
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002054 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055*/
2056
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002057size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 SkDEBUGCODE(this->validate();)
2059
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002060 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002061 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002062 return SkAlign4(byteCount);
2063 }
2064
2065 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002066
robertphillips@google.com466310d2013-12-03 16:43:54 +00002067 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002068 (fFillType << kFillType_SerializationShift) |
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002069 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002070
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002071 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002072
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002073 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002074
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002075 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002076 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077}
2078
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002079size_t SkPath::readFromMemory(const void* storage, size_t length) {
2080 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002082 int32_t packed;
2083 if (!buffer.readS32(&packed)) {
2084 return 0;
2085 }
2086
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002087 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2088 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002089 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002090 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002091
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002092 size_t sizeRead = 0;
2093 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002094 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002095 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002096 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002097 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002098 } else if (NULL != pathRef) {
2099 // If the buffer is not valid, pathRef should be NULL
2100 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002101 }
2102 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103}
2104
2105///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106
reed@google.com51bbe752013-01-17 22:07:50 +00002107#include "SkString.h"
2108
2109static void append_scalar(SkString* str, SkScalar value) {
2110 SkString tmp;
2111 tmp.printf("%g", value);
2112 if (tmp.contains('.')) {
2113 tmp.appendUnichar('f');
2114 }
2115 str->append(tmp);
2116}
2117
2118static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002119 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002120 str->append(label);
2121 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002122
reed@google.com51bbe752013-01-17 22:07:50 +00002123 const SkScalar* values = &pts[0].fX;
2124 count *= 2;
2125
2126 for (int i = 0; i < count; ++i) {
2127 append_scalar(str, values[i]);
2128 if (i < count - 1) {
2129 str->append(", ");
2130 }
2131 }
reed@google.com277c3f82013-05-31 15:17:50 +00002132 if (conicWeight >= 0) {
2133 str->append(", ");
2134 append_scalar(str, conicWeight);
2135 }
reed@google.com51bbe752013-01-17 22:07:50 +00002136 str->append(");\n");
2137}
2138
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139void SkPath::dump(bool forceClose, const char title[]) const {
2140 Iter iter(*this, forceClose);
2141 SkPoint pts[4];
2142 Verb verb;
2143
2144 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2145 title ? title : "");
2146
reed@google.com51bbe752013-01-17 22:07:50 +00002147 SkString builder;
2148
reed@google.com4a3b7142012-05-16 17:16:46 +00002149 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 switch (verb) {
2151 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002152 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002155 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
2157 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002158 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002160 case kConic_Verb:
2161 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2162 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002164 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 break;
2166 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002167 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 break;
2169 default:
2170 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2171 verb = kDone_Verb; // stop the loop
2172 break;
2173 }
2174 }
reed@google.com51bbe752013-01-17 22:07:50 +00002175 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002176}
2177
reed@android.come522ca52009-11-23 20:10:41 +00002178void SkPath::dump() const {
2179 this->dump(false);
2180}
2181
2182#ifdef SK_DEBUG
2183void SkPath::validate() const {
2184 SkASSERT(this != NULL);
2185 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002186
djsollen@google.com077348c2012-10-22 20:23:32 +00002187#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002188 if (!fBoundsIsDirty) {
2189 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002190
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002191 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002192 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002193
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002194 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002195 // if we're empty, fBounds may be empty but translated, so we can't
2196 // necessarily compare to bounds directly
2197 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2198 // be [2, 2, 2, 2]
2199 SkASSERT(bounds.isEmpty());
2200 SkASSERT(fBounds.isEmpty());
2201 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 if (bounds.isEmpty()) {
2203 SkASSERT(fBounds.isEmpty());
2204 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002205 if (!fBounds.isEmpty()) {
2206 SkASSERT(fBounds.contains(bounds));
2207 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 }
reed@android.come522ca52009-11-23 20:10:41 +00002209 }
2210 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002211#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002212}
djsollen@google.com077348c2012-10-22 20:23:32 +00002213#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002214
reed@google.com04863fa2011-05-15 04:08:24 +00002215///////////////////////////////////////////////////////////////////////////////
2216
reed@google.com0b7b9822011-05-16 12:29:27 +00002217static int sign(SkScalar x) { return x < 0; }
2218#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002219
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002220static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2221 // The error epsilon was empirically derived; worse case round rects
2222 // with a mid point outset by 2x float epsilon in tests had an error
2223 // of 12.
2224 const int epsilon = 16;
2225 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2226 return false;
2227 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002228 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002229 int aBits = SkFloatAs2sCompliment(compA);
2230 int bBits = SkFloatAs2sCompliment(compB);
2231 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002232}
2233
2234// only valid for a single contour
2235struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002236 Convexicator()
2237 : fPtCount(0)
2238 , fConvexity(SkPath::kConvex_Convexity)
2239 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002240 fSign = 0;
2241 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002242 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002243 fCurrPt.set(0, 0);
2244 fVec0.set(0, 0);
2245 fVec1.set(0, 0);
2246 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002247
2248 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002249 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002250 }
2251
2252 SkPath::Convexity getConvexity() const { return fConvexity; }
2253
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002254 /** The direction returned is only valid if the path is determined convex */
2255 SkPath::Direction getDirection() const { return fDirection; }
2256
reed@google.com04863fa2011-05-15 04:08:24 +00002257 void addPt(const SkPoint& pt) {
2258 if (SkPath::kConcave_Convexity == fConvexity) {
2259 return;
2260 }
2261
2262 if (0 == fPtCount) {
2263 fCurrPt = pt;
2264 ++fPtCount;
2265 } else {
2266 SkVector vec = pt - fCurrPt;
2267 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002268 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002269 fCurrPt = pt;
2270 if (++fPtCount == 2) {
2271 fFirstVec = fVec1 = vec;
2272 } else {
2273 SkASSERT(fPtCount > 2);
2274 this->addVec(vec);
2275 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002276
reed@google.com85b6e392011-05-15 20:25:17 +00002277 int sx = sign(vec.fX);
2278 int sy = sign(vec.fY);
2279 fDx += (sx != fSx);
2280 fDy += (sy != fSy);
2281 fSx = sx;
2282 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002283
reed@google.com85b6e392011-05-15 20:25:17 +00002284 if (fDx > 3 || fDy > 3) {
2285 fConvexity = SkPath::kConcave_Convexity;
2286 }
reed@google.com04863fa2011-05-15 04:08:24 +00002287 }
2288 }
2289 }
2290
2291 void close() {
2292 if (fPtCount > 2) {
2293 this->addVec(fFirstVec);
2294 }
2295 }
2296
2297private:
2298 void addVec(const SkVector& vec) {
2299 SkASSERT(vec.fX || vec.fY);
2300 fVec0 = fVec1;
2301 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002302 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2303 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2304 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2305 largest = SkTMax(largest, -smallest);
2306 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002307 if (0 == fSign) {
2308 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002309 if (1 == sign) {
2310 fDirection = SkPath::kCW_Direction;
2311 } else if (-1 == sign) {
2312 fDirection = SkPath::kCCW_Direction;
2313 }
reed@google.com04863fa2011-05-15 04:08:24 +00002314 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002315 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002316 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002317 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002318 }
2319 }
2320 }
2321
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002322 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002323 SkPoint fCurrPt;
2324 SkVector fVec0, fVec1, fFirstVec;
2325 int fPtCount; // non-degenerate points
2326 int fSign;
2327 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002329 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002330};
2331
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002332SkPath::Convexity SkPath::internalGetConvexity() const {
2333 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002334 SkPoint pts[4];
2335 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002336 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002337
2338 int contourCount = 0;
2339 int count;
2340 Convexicator state;
2341
2342 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2343 switch (verb) {
2344 case kMove_Verb:
2345 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002346 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002347 return kConcave_Convexity;
2348 }
2349 pts[1] = pts[0];
2350 count = 1;
2351 break;
2352 case kLine_Verb: count = 1; break;
2353 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002354 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 case kCubic_Verb: count = 3; break;
2356 case kClose_Verb:
2357 state.close();
2358 count = 0;
2359 break;
2360 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002361 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002362 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002363 return kConcave_Convexity;
2364 }
2365
2366 for (int i = 1; i <= count; i++) {
2367 state.addPt(pts[i]);
2368 }
2369 // early exit
2370 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002371 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002372 return kConcave_Convexity;
2373 }
2374 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002375 fConvexity = state.getConvexity();
2376 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2377 fDirection = state.getDirection();
2378 }
2379 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002380}
reed@google.com69a99432012-01-10 18:00:10 +00002381
2382///////////////////////////////////////////////////////////////////////////////
2383
2384class ContourIter {
2385public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002386 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002387
2388 bool done() const { return fDone; }
2389 // if !done() then these may be called
2390 int count() const { return fCurrPtCount; }
2391 const SkPoint* pts() const { return fCurrPt; }
2392 void next();
2393
2394private:
2395 int fCurrPtCount;
2396 const SkPoint* fCurrPt;
2397 const uint8_t* fCurrVerb;
2398 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002399 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002400 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002401 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002402};
2403
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002404ContourIter::ContourIter(const SkPathRef& pathRef) {
2405 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002406 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002407 fCurrPt = pathRef.points();
2408 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002409 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002410 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002411 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002412 this->next();
2413}
2414
2415void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002416 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002417 fDone = true;
2418 }
2419 if (fDone) {
2420 return;
2421 }
2422
2423 // skip pts of prev contour
2424 fCurrPt += fCurrPtCount;
2425
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002426 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002427 int ptCount = 1; // moveTo
2428 const uint8_t* verbs = fCurrVerb;
2429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430 for (--verbs; verbs > fStopVerbs; --verbs) {
2431 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002432 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002433 goto CONTOUR_END;
2434 case SkPath::kLine_Verb:
2435 ptCount += 1;
2436 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002437 case SkPath::kConic_Verb:
2438 fCurrConicWeight += 1;
2439 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002440 case SkPath::kQuad_Verb:
2441 ptCount += 2;
2442 break;
2443 case SkPath::kCubic_Verb:
2444 ptCount += 3;
2445 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002446 case SkPath::kClose_Verb:
2447 break;
2448 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002449 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002450 break;
2451 }
2452 }
2453CONTOUR_END:
2454 fCurrPtCount = ptCount;
2455 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002456 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002457}
2458
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002459// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002460static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002461 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2462 // We may get 0 when the above subtracts underflow. We expect this to be
2463 // very rare and lazily promote to double.
2464 if (0 == cross) {
2465 double p0x = SkScalarToDouble(p0.fX);
2466 double p0y = SkScalarToDouble(p0.fY);
2467
2468 double p1x = SkScalarToDouble(p1.fX);
2469 double p1y = SkScalarToDouble(p1.fY);
2470
2471 double p2x = SkScalarToDouble(p2.fX);
2472 double p2y = SkScalarToDouble(p2.fY);
2473
2474 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2475 (p1y - p0y) * (p2x - p0x));
2476
2477 }
2478 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002479}
2480
reed@google.comc1ea60a2012-01-31 15:15:36 +00002481// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002482static int find_max_y(const SkPoint pts[], int count) {
2483 SkASSERT(count > 0);
2484 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002485 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002486 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002487 SkScalar y = pts[i].fY;
2488 if (y > max) {
2489 max = y;
2490 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002491 }
2492 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002493 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002494}
2495
reed@google.comcabaf1d2012-01-11 21:03:05 +00002496static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2497 int i = index;
2498 for (;;) {
2499 i = (i + inc) % n;
2500 if (i == index) { // we wrapped around, so abort
2501 break;
2502 }
2503 if (pts[index] != pts[i]) { // found a different point, success!
2504 break;
2505 }
2506 }
2507 return i;
2508}
2509
reed@google.comc1ea60a2012-01-31 15:15:36 +00002510/**
2511 * Starting at index, and moving forward (incrementing), find the xmin and
2512 * xmax of the contiguous points that have the same Y.
2513 */
2514static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2515 int* maxIndexPtr) {
2516 const SkScalar y = pts[index].fY;
2517 SkScalar min = pts[index].fX;
2518 SkScalar max = min;
2519 int minIndex = index;
2520 int maxIndex = index;
2521 for (int i = index + 1; i < n; ++i) {
2522 if (pts[i].fY != y) {
2523 break;
2524 }
2525 SkScalar x = pts[i].fX;
2526 if (x < min) {
2527 min = x;
2528 minIndex = i;
2529 } else if (x > max) {
2530 max = x;
2531 maxIndex = i;
2532 }
2533 }
2534 *maxIndexPtr = maxIndex;
2535 return minIndex;
2536}
2537
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002538static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002539 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002540}
2541
reed@google.comac8543f2012-01-30 20:51:25 +00002542/*
2543 * We loop through all contours, and keep the computed cross-product of the
2544 * contour that contained the global y-max. If we just look at the first
2545 * contour, we may find one that is wound the opposite way (correctly) since
2546 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2547 * that is outer most (or at least has the global y-max) before we can consider
2548 * its cross product.
2549 */
reed@google.com69a99432012-01-10 18:00:10 +00002550bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002551 if (kUnknown_Direction != fDirection) {
2552 *dir = static_cast<Direction>(fDirection);
2553 return true;
2554 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002555
2556 // don't want to pay the cost for computing this if it
2557 // is unknown, so we don't call isConvex()
2558 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2559 SkASSERT(kUnknown_Direction == fDirection);
2560 *dir = static_cast<Direction>(fDirection);
2561 return false;
2562 }
reed@google.com69a99432012-01-10 18:00:10 +00002563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002564 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002565
reed@google.comac8543f2012-01-30 20:51:25 +00002566 // initialize with our logical y-min
2567 SkScalar ymax = this->getBounds().fTop;
2568 SkScalar ymaxCross = 0;
2569
reed@google.com69a99432012-01-10 18:00:10 +00002570 for (; !iter.done(); iter.next()) {
2571 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002572 if (n < 3) {
2573 continue;
2574 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002575
reed@google.comcabaf1d2012-01-11 21:03:05 +00002576 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002577 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002578 int index = find_max_y(pts, n);
2579 if (pts[index].fY < ymax) {
2580 continue;
2581 }
2582
2583 // If there is more than 1 distinct point at the y-max, we take the
2584 // x-min and x-max of them and just subtract to compute the dir.
2585 if (pts[(index + 1) % n].fY == pts[index].fY) {
2586 int maxIndex;
2587 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2588 if (minIndex == maxIndex) {
2589 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002590 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002591 SkASSERT(pts[minIndex].fY == pts[index].fY);
2592 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2593 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2594 // we just subtract the indices, and let that auto-convert to
2595 // SkScalar, since we just want - or + to signal the direction.
2596 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002597 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002598 TRY_CROSSPROD:
2599 // Find a next and prev index to use for the cross-product test,
2600 // but we try to find pts that form non-zero vectors from pts[index]
2601 //
2602 // Its possible that we can't find two non-degenerate vectors, so
2603 // we have to guard our search (e.g. all the pts could be in the
2604 // same place).
2605
2606 // we pass n - 1 instead of -1 so we don't foul up % operator by
2607 // passing it a negative LH argument.
2608 int prev = find_diff_pt(pts, index, n, n - 1);
2609 if (prev == index) {
2610 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002611 continue;
2612 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002613 int next = find_diff_pt(pts, index, n, 1);
2614 SkASSERT(next != index);
2615 cross = cross_prod(pts[prev], pts[index], pts[next]);
2616 // if we get a zero and the points are horizontal, then we look at the spread in
2617 // x-direction. We really should continue to walk away from the degeneracy until
2618 // there is a divergence.
2619 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2620 // construct the subtract so we get the correct Direction below
2621 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002622 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002623 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002624
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002625 if (cross) {
2626 // record our best guess so far
2627 ymax = pts[index].fY;
2628 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002629 }
2630 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002631 if (ymaxCross) {
2632 crossToDir(ymaxCross, dir);
2633 fDirection = *dir;
2634 return true;
2635 } else {
2636 return false;
2637 }
reed@google.comac8543f2012-01-30 20:51:25 +00002638}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002639
2640///////////////////////////////////////////////////////////////////////////////
2641
2642static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2643 SkScalar D, SkScalar t) {
2644 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2645}
2646
2647static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2648 SkScalar t) {
2649 SkScalar A = c3 + 3*(c1 - c2) - c0;
2650 SkScalar B = 3*(c2 - c1 - c1 + c0);
2651 SkScalar C = 3*(c1 - c0);
2652 SkScalar D = c0;
2653 return eval_cubic_coeff(A, B, C, D, t);
2654}
2655
2656/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2657 t value such that cubic(t) = target
2658 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002659static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002660 SkScalar target, SkScalar* t) {
2661 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2662 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002663
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002664 SkScalar D = c0 - target;
2665 SkScalar A = c3 + 3*(c1 - c2) - c0;
2666 SkScalar B = 3*(c2 - c1 - c1 + c0);
2667 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002668
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002669 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2670 SkScalar minT = 0;
2671 SkScalar maxT = SK_Scalar1;
2672 SkScalar mid;
2673 int i;
2674 for (i = 0; i < 16; i++) {
2675 mid = SkScalarAve(minT, maxT);
2676 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2677 if (delta < 0) {
2678 minT = mid;
2679 delta = -delta;
2680 } else {
2681 maxT = mid;
2682 }
2683 if (delta < TOLERANCE) {
2684 break;
2685 }
2686 }
2687 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002688}
2689
2690template <size_t N> static void find_minmax(const SkPoint pts[],
2691 SkScalar* minPtr, SkScalar* maxPtr) {
2692 SkScalar min, max;
2693 min = max = pts[0].fX;
2694 for (size_t i = 1; i < N; ++i) {
2695 min = SkMinScalar(min, pts[i].fX);
2696 max = SkMaxScalar(max, pts[i].fX);
2697 }
2698 *minPtr = min;
2699 *maxPtr = max;
2700}
2701
2702static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2703 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002704
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002705 int dir = 1;
2706 if (pts[0].fY > pts[3].fY) {
2707 storage[0] = pts[3];
2708 storage[1] = pts[2];
2709 storage[2] = pts[1];
2710 storage[3] = pts[0];
2711 pts = storage;
2712 dir = -1;
2713 }
2714 if (y < pts[0].fY || y >= pts[3].fY) {
2715 return 0;
2716 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002717
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002718 // quickreject or quickaccept
2719 SkScalar min, max;
2720 find_minmax<4>(pts, &min, &max);
2721 if (x < min) {
2722 return 0;
2723 }
2724 if (x > max) {
2725 return dir;
2726 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002729 SkScalar t;
2730 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2731 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 +00002732 return xt < x ? dir : 0;
2733}
2734
2735static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2736 SkPoint dst[10];
2737 int n = SkChopCubicAtYExtrema(pts, dst);
2738 int w = 0;
2739 for (int i = 0; i <= n; ++i) {
2740 w += winding_mono_cubic(&dst[i * 3], x, y);
2741 }
2742 return w;
2743}
2744
2745static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2746 SkScalar y0 = pts[0].fY;
2747 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749 int dir = 1;
2750 if (y0 > y2) {
2751 SkTSwap(y0, y2);
2752 dir = -1;
2753 }
2754 if (y < y0 || y >= y2) {
2755 return 0;
2756 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002757
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758 // bounds check on X (not required. is it faster?)
2759#if 0
2760 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2761 return 0;
2762 }
2763#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 SkScalar roots[2];
2766 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2767 2 * (pts[1].fY - pts[0].fY),
2768 pts[0].fY - y,
2769 roots);
2770 SkASSERT(n <= 1);
2771 SkScalar xt;
2772 if (0 == n) {
2773 SkScalar mid = SkScalarAve(y0, y2);
2774 // Need [0] and [2] if dir == 1
2775 // and [2] and [0] if dir == -1
2776 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2777 } else {
2778 SkScalar t = roots[0];
2779 SkScalar C = pts[0].fX;
2780 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2781 SkScalar B = 2 * (pts[1].fX - C);
2782 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2783 }
2784 return xt < x ? dir : 0;
2785}
2786
2787static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2788 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2789 if (y0 == y1) {
2790 return true;
2791 }
2792 if (y0 < y1) {
2793 return y1 <= y2;
2794 } else {
2795 return y1 >= y2;
2796 }
2797}
2798
2799static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2800 SkPoint dst[5];
2801 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002802
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2804 n = SkChopQuadAtYExtrema(pts, dst);
2805 pts = dst;
2806 }
2807 int w = winding_mono_quad(pts, x, y);
2808 if (n > 0) {
2809 w += winding_mono_quad(&pts[2], x, y);
2810 }
2811 return w;
2812}
2813
2814static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2815 SkScalar x0 = pts[0].fX;
2816 SkScalar y0 = pts[0].fY;
2817 SkScalar x1 = pts[1].fX;
2818 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 int dir = 1;
2823 if (y0 > y1) {
2824 SkTSwap(y0, y1);
2825 dir = -1;
2826 }
2827 if (y < y0 || y >= y1) {
2828 return 0;
2829 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002830
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2832 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002833
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 if (SkScalarSignAsInt(cross) == dir) {
2835 dir = 0;
2836 }
2837 return dir;
2838}
2839
reed@google.com4db592c2013-10-30 17:39:43 +00002840static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2841 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2842}
2843
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844bool SkPath::contains(SkScalar x, SkScalar y) const {
2845 bool isInverse = this->isInverseFillType();
2846 if (this->isEmpty()) {
2847 return isInverse;
2848 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002849
reed@google.com4db592c2013-10-30 17:39:43 +00002850 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851 return isInverse;
2852 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002853
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002854 SkPath::Iter iter(*this, true);
2855 bool done = false;
2856 int w = 0;
2857 do {
2858 SkPoint pts[4];
2859 switch (iter.next(pts, false)) {
2860 case SkPath::kMove_Verb:
2861 case SkPath::kClose_Verb:
2862 break;
2863 case SkPath::kLine_Verb:
2864 w += winding_line(pts, x, y);
2865 break;
2866 case SkPath::kQuad_Verb:
2867 w += winding_quad(pts, x, y);
2868 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002869 case SkPath::kConic_Verb:
2870 SkASSERT(0);
2871 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 case SkPath::kCubic_Verb:
2873 w += winding_cubic(pts, x, y);
2874 break;
2875 case SkPath::kDone_Verb:
2876 done = true;
2877 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002878 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 switch (this->getFillType()) {
2882 case SkPath::kEvenOdd_FillType:
2883 case SkPath::kInverseEvenOdd_FillType:
2884 w &= 1;
2885 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002886 default:
2887 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 }
2889 return SkToBool(w);
2890}