blob: 7f2e67a327ac2538e7cae5879056fa343ae2a121 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
41 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
42 }
43
44 ~SkAutoDisableDirectionCheck() {
45 fPath->fDirection = fSaved;
46 }
47
48private:
49 SkPath* fPath;
50 SkPath::Direction fSaved;
51};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000129 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000131 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132#endif
133{
134 this->resetFields();
135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181}
182
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000183bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000184 // note: don't need to look at isConvex or bounds, since just comparing the
185 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000186 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000187 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190void SkPath::swap(SkPath& that) {
191 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 if (this != &that) {
194 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000195 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
198 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000199#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000200 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
201#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000205static inline bool check_edge_against_rect(const SkPoint& p0,
206 const SkPoint& p1,
207 const SkRect& rect,
208 SkPath::Direction dir) {
209 const SkPoint* edgeBegin;
210 SkVector v;
211 if (SkPath::kCW_Direction == dir) {
212 v = p1 - p0;
213 edgeBegin = &p0;
214 } else {
215 v = p0 - p1;
216 edgeBegin = &p1;
217 }
218 if (v.fX || v.fY) {
219 // check the cross product of v with the vec from edgeBegin to each rect corner
220 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
221 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
222 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
223 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
224 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
225 return false;
226 }
227 }
228 return true;
229}
230
231bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
232 // This only handles non-degenerate convex paths currently.
233 if (kConvex_Convexity != this->getConvexity()) {
234 return false;
235 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237 Direction direction;
238 if (!this->cheapComputeDirection(&direction)) {
239 return false;
240 }
241
242 SkPoint firstPt;
243 SkPoint prevPt;
244 RawIter iter(*this);
245 SkPath::Verb verb;
246 SkPoint pts[4];
247 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000248 SkDEBUGCODE(int segmentCount = 0;)
249 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250
251 while ((verb = iter.next(pts)) != kDone_Verb) {
252 int nextPt = -1;
253 switch (verb) {
254 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000256 SkDEBUGCODE(++moveCnt);
257 firstPt = prevPt = pts[0];
258 break;
259 case kLine_Verb:
260 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 break;
264 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000265 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkASSERT(moveCnt && !closeCount);
267 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 nextPt = 2;
269 break;
270 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkASSERT(moveCnt && !closeCount);
272 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273 nextPt = 3;
274 break;
275 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000276 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 break;
278 default:
279 SkDEBUGFAIL("unknown verb");
280 }
281 if (-1 != nextPt) {
282 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
283 return false;
284 }
285 prevPt = pts[nextPt];
286 }
287 }
288
289 return check_edge_against_rect(prevPt, firstPt, rect, direction);
290}
291
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000292uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000293 uint32_t genID = fPathRef->genID();
294#ifdef SK_BUILD_FOR_ANDROID
295 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
296 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
297#endif
298 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000299}
djsollen@google.come63793a2012-03-21 15:39:03 +0000300
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000302const SkPath* SkPath::getSourcePath() const {
303 return fSourcePath;
304}
305
306void SkPath::setSourcePath(const SkPath* path) {
307 fSourcePath = path;
308}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000309#endif
310
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311void SkPath::reset() {
312 SkDEBUGCODE(this->validate();)
313
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000314 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000315 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316}
317
318void SkPath::rewind() {
319 SkDEBUGCODE(this->validate();)
320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000322 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323}
324
reed@google.com7e6c4d12012-05-10 14:05:43 +0000325bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000326 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000327
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000328 if (2 == verbCount) {
329 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
330 if (kLine_Verb == fPathRef->atVerb(1)) {
331 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000332 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000334 line[0] = pts[0];
335 line[1] = pts[1];
336 }
337 return true;
338 }
339 }
340 return false;
341}
342
caryclark@google.comf1316942011-07-26 19:54:45 +0000343/*
344 Determines if path is a rect by keeping track of changes in direction
345 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347 The direction is computed such that:
348 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000349 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000350 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000351 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000352
caryclark@google.comf1316942011-07-26 19:54:45 +0000353A rectangle cycles up/right/down/left or up/left/down/right.
354
355The test fails if:
356 The path is closed, and followed by a line.
357 A second move creates a new endpoint.
358 A diagonal line is parsed.
359 There's more than four changes of direction.
360 There's a discontinuity on the line (e.g., a move in the middle)
361 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000362 The path contains a quadratic or cubic.
363 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000364 *The rectangle doesn't complete a cycle.
365 *The final point isn't equal to the first point.
366
367 *These last two conditions we relax if we have a 3-edge path that would
368 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370It's OK if the path has:
371 Several colinear line segments composing a rectangle side.
372 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374The direction takes advantage of the corners found since opposite sides
375must travel in opposite directions.
376
377FIXME: Allow colinear quads and cubics to be treated like lines.
378FIXME: If the API passes fill-only, return true if the filled stroke
379 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000380
381 first,last,next direction state-machine:
382 0x1 is set if the segment is horizontal
383 0x2 is set if the segment is moving to the right or down
384 thus:
385 two directions are opposites iff (dirA ^ dirB) == 0x2
386 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000389static int rect_make_dir(SkScalar dx, SkScalar dy) {
390 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
391}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
393 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 int corners = 0;
395 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000396 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000397 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000398 first.set(0, 0);
399 last.set(0, 0);
400 int firstDirection = 0;
401 int lastDirection = 0;
402 int nextDirection = 0;
403 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000405 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
407 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 savePts = pts;
410 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 autoClose = true;
412 case kLine_Verb: {
413 SkScalar left = last.fX;
414 SkScalar top = last.fY;
415 SkScalar right = pts->fX;
416 SkScalar bottom = pts->fY;
417 ++pts;
418 if (left != right && top != bottom) {
419 return false; // diagonal
420 }
421 if (left == right && top == bottom) {
422 break; // single point on side OK
423 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 if (0 == corners) {
426 firstDirection = nextDirection;
427 first = last;
428 last = pts[-1];
429 corners = 1;
430 closedOrMoved = false;
431 break;
432 }
433 if (closedOrMoved) {
434 return false; // closed followed by a line
435 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000436 if (autoClose && nextDirection == firstDirection) {
437 break; // colinear with first
438 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 closedOrMoved = autoClose;
440 if (lastDirection != nextDirection) {
441 if (++corners > 4) {
442 return false; // too many direction changes
443 }
444 }
445 last = pts[-1];
446 if (lastDirection == nextDirection) {
447 break; // colinear segment
448 }
449 // Possible values for corners are 2, 3, and 4.
450 // When corners == 3, nextDirection opposes firstDirection.
451 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
454 if ((directionCycle ^ turn) != nextDirection) {
455 return false; // direction didn't follow cycle
456 }
457 break;
458 }
459 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000460 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 case kCubic_Verb:
462 return false; // quadratic, cubic not allowed
463 case kMove_Verb:
464 last = *pts++;
465 closedOrMoved = true;
466 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000467 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000468 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000469 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 lastDirection = nextDirection;
473 }
474 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000476 if (!result) {
477 // check if we are just an incomplete rectangle, in which case we can
478 // return true, but not claim to be closed.
479 // e.g.
480 // 3 sided rectangle
481 // 4 sided but the last edge is not long enough to reach the start
482 //
483 SkScalar closeX = first.x() - last.x();
484 SkScalar closeY = first.y() - last.y();
485 if (closeX && closeY) {
486 return false; // we're diagonal, abort (can we ever reach this?)
487 }
488 int closeDirection = rect_make_dir(closeX, closeY);
489 // make sure the close-segment doesn't double-back on itself
490 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
491 result = true;
492 autoClose = false; // we are not closed
493 }
494 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000507SkPath::PathAsRect SkPath::asRect(Direction* direction) const {
508 SK_COMPILE_ASSERT(0 == kNone_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.org7e90e8d2014-02-11 01:38:30 +0000509 SK_COMPILE_ASSERT(1 == kFill_PathAsRect, path_as_rect_mismatch);
510 SK_COMPILE_ASSERT(2 == kStroke_PathAsRect, path_as_rect_mismatch);
commit-bot@chromium.orgc2abd542014-01-25 16:54:31 +0000511 bool isClosed = false;
512 return (PathAsRect) (isRect(&isClosed, direction) + isClosed);
513}
514
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000516 SkDEBUGCODE(this->validate();)
517 int currVerb = 0;
518 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000519 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
520 if (result && rect) {
521 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000522 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000523 return result;
524}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000525
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000526bool SkPath::isRect(bool* isClosed, Direction* direction) const {
527 SkDEBUGCODE(this->validate();)
528 int currVerb = 0;
529 const SkPoint* pts = fPathRef->points();
530 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000531}
532
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000533bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 SkDEBUGCODE(this->validate();)
535 int currVerb = 0;
536 const SkPoint* pts = fPathRef->points();
537 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 Direction testDirs[2];
539 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000540 return false;
541 }
542 const SkPoint* last = pts;
543 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[0].set(first, SkToS32(last - first));
546 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 if (testRects[0].contains(testRects[1])) {
548 if (rects) {
549 rects[0] = testRects[0];
550 rects[1] = testRects[1];
551 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000552 if (dirs) {
553 dirs[0] = testDirs[0];
554 dirs[1] = testDirs[1];
555 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 return true;
557 }
558 if (testRects[1].contains(testRects[0])) {
559 if (rects) {
560 rects[0] = testRects[1];
561 rects[1] = testRects[0];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[1];
565 dirs[1] = testDirs[0];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 }
570 return false;
571}
572
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000573int SkPath::countPoints() const {
574 return fPathRef->countPoints();
575}
576
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000577int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 SkDEBUGCODE(this->validate();)
579
580 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000581 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 int count = SkMin32(max, fPathRef->countPoints());
583 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
584 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585}
586
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000587SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
589 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590 }
591 return SkPoint::Make(0, 0);
592}
593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594int SkPath::countVerbs() const {
595 return fPathRef->countVerbs();
596}
597
598static inline void copy_verbs_reverse(uint8_t* inorderDst,
599 const uint8_t* reversedSrc,
600 int count) {
601 for (int i = 0; i < count; ++i) {
602 inorderDst[i] = reversedSrc[~i];
603 }
604}
605
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606int SkPath::getVerbs(uint8_t dst[], int max) const {
607 SkDEBUGCODE(this->validate();)
608
609 SkASSERT(max >= 0);
610 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = SkMin32(max, fPathRef->countVerbs());
612 copy_verbs_reverse(dst, fPathRef->verbs(), count);
613 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000614}
615
reed@google.com294dd7b2011-10-11 11:58:32 +0000616bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 SkDEBUGCODE(this->validate();)
618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000619 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (count > 0) {
621 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000624 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 if (lastPt) {
627 lastPt->set(0, 0);
628 }
629 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630}
631
632void SkPath::setLastPt(SkScalar x, SkScalar y) {
633 SkDEBUGCODE(this->validate();)
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 if (count == 0) {
637 this->moveTo(x, y);
638 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 SkPathRef::Editor ed(&fPathRef);
640 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 }
642}
643
reed@google.com04863fa2011-05-15 04:08:24 +0000644void SkPath::setConvexity(Convexity c) {
645 if (fConvexity != c) {
646 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000647 }
648}
649
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650//////////////////////////////////////////////////////////////////////////////
651// Construction methods
652
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000653#define DIRTY_AFTER_EDIT \
654 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000655 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000656 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000657 } while (0)
658
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659void SkPath::incReserve(U16CPU inc) {
660 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 SkDEBUGCODE(this->validate();)
663}
664
665void SkPath::moveTo(SkScalar x, SkScalar y) {
666 SkDEBUGCODE(this->validate();)
667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000670 // remember our index
671 fLastMoveToIndex = fPathRef->countPoints();
672
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 ed.growForVerb(kMove_Verb)->set(x, y);
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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000682void SkPath::injectMoveToIfNeeded() {
683 if (fLastMoveToIndex < 0) {
684 SkScalar x, y;
685 if (fPathRef->countVerbs() == 0) {
686 x = y = 0;
687 } else {
688 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
689 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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000717 this->injectMoveToIfNeeded();
718
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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000747 this->injectMoveToIfNeeded();
748
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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +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 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000863 fLastMoveToIndex = fPathRef->countPoints();
864
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
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001361 fLastMoveToIndex = fPathRef->countPoints();
1362
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001363 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
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001446void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 SkMatrix matrix;
1448
1449 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001450 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451}
1452
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001453void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001456 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkPoint pts[4];
1458 Verb verb;
1459
1460 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001461 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 while ((verb = iter.next(pts)) != kDone_Verb) {
1463 switch (verb) {
1464 case kMove_Verb:
1465 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001466 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1467 injectMoveToIfNeeded(); // In case last contour is closed
1468 this->lineTo(pts[0]);
1469 } else {
1470 this->moveTo(pts[0]);
1471 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472 break;
1473 case kLine_Verb:
1474 proc(matrix, &pts[1], &pts[1], 1);
1475 this->lineTo(pts[1]);
1476 break;
1477 case kQuad_Verb:
1478 proc(matrix, &pts[1], &pts[1], 2);
1479 this->quadTo(pts[1], pts[2]);
1480 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001481 case kConic_Verb:
1482 proc(matrix, &pts[1], &pts[1], 2);
1483 this->conicTo(pts[1], pts[2], iter.conicWeight());
1484 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 case kCubic_Verb:
1486 proc(matrix, &pts[1], &pts[1], 3);
1487 this->cubicTo(pts[1], pts[2], pts[3]);
1488 break;
1489 case kClose_Verb:
1490 this->close();
1491 break;
1492 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001493 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001495 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 }
1497}
1498
1499///////////////////////////////////////////////////////////////////////////////
1500
reed@google.com277c3f82013-05-31 15:17:50 +00001501static int pts_in_verb(unsigned verb) {
1502 static const uint8_t gPtsInVerb[] = {
1503 1, // kMove
1504 1, // kLine
1505 2, // kQuad
1506 2, // kConic
1507 3, // kCubic
1508 0, // kClose
1509 0 // kDone
1510 };
1511
1512 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1513 return gPtsInVerb[verb];
1514}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516// ignore the last point of the 1st contour
1517void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 int i, vcount = path.fPathRef->countVerbs();
1519 // exit early if the path is empty, or just has a moveTo.
1520 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 return;
1522 }
1523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 const uint8_t* verbs = path.fPathRef->verbs();
1527 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001528 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001530 SkASSERT(verbs[~0] == kMove_Verb);
1531 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001532 unsigned v = verbs[~i];
1533 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 if (n == 0) {
1535 break;
1536 }
1537 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001538 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 }
1540
1541 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 case kLine_Verb:
1544 this->lineTo(pts[-1].fX, pts[-1].fY);
1545 break;
1546 case kQuad_Verb:
1547 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1548 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001549 case kConic_Verb:
1550 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1551 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 case kCubic_Verb:
1553 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1554 pts[-3].fX, pts[-3].fY);
1555 break;
1556 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001557 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 break;
1559 }
reed@google.com277c3f82013-05-31 15:17:50 +00001560 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001561 }
1562}
1563
reed@google.com63d73742012-01-10 15:33:12 +00001564void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001566
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001567 const SkPoint* pts = src.fPathRef->pointsEnd();
1568 // we will iterator through src's verbs backwards
1569 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1570 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001571 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001572
1573 bool needMove = true;
1574 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001575 while (verbs < verbsEnd) {
1576 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001577 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001578
1579 if (needMove) {
1580 --pts;
1581 this->moveTo(pts->fX, pts->fY);
1582 needMove = false;
1583 }
1584 pts -= n;
1585 switch (v) {
1586 case kMove_Verb:
1587 if (needClose) {
1588 this->close();
1589 needClose = false;
1590 }
1591 needMove = true;
1592 pts += 1; // so we see the point in "if (needMove)" above
1593 break;
1594 case kLine_Verb:
1595 this->lineTo(pts[0]);
1596 break;
1597 case kQuad_Verb:
1598 this->quadTo(pts[1], pts[0]);
1599 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001600 case kConic_Verb:
1601 this->conicTo(pts[1], pts[0], *--conicWeights);
1602 break;
reed@google.com63d73742012-01-10 15:33:12 +00001603 case kCubic_Verb:
1604 this->cubicTo(pts[2], pts[1], pts[0]);
1605 break;
1606 case kClose_Verb:
1607 needClose = true;
1608 break;
1609 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001610 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001611 }
1612 }
1613}
1614
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615///////////////////////////////////////////////////////////////////////////////
1616
1617void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1618 SkMatrix matrix;
1619
1620 matrix.setTranslate(dx, dy);
1621 this->transform(matrix, dst);
1622}
1623
1624#include "SkGeometry.h"
1625
1626static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1627 int level = 2) {
1628 if (--level >= 0) {
1629 SkPoint tmp[5];
1630
1631 SkChopQuadAtHalf(pts, tmp);
1632 subdivide_quad_to(path, &tmp[0], level);
1633 subdivide_quad_to(path, &tmp[2], level);
1634 } else {
1635 path->quadTo(pts[1], pts[2]);
1636 }
1637}
1638
1639static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1640 int level = 2) {
1641 if (--level >= 0) {
1642 SkPoint tmp[7];
1643
1644 SkChopCubicAtHalf(pts, tmp);
1645 subdivide_cubic_to(path, &tmp[0], level);
1646 subdivide_cubic_to(path, &tmp[3], level);
1647 } else {
1648 path->cubicTo(pts[1], pts[2], pts[3]);
1649 }
1650}
1651
1652void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1653 SkDEBUGCODE(this->validate();)
1654 if (dst == NULL) {
1655 dst = (SkPath*)this;
1656 }
1657
tomhudson@google.com8d430182011-06-06 19:11:19 +00001658 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 SkPath tmp;
1660 tmp.fFillType = fFillType;
1661
1662 SkPath::Iter iter(*this, false);
1663 SkPoint pts[4];
1664 SkPath::Verb verb;
1665
reed@google.com4a3b7142012-05-16 17:16:46 +00001666 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 switch (verb) {
1668 case kMove_Verb:
1669 tmp.moveTo(pts[0]);
1670 break;
1671 case kLine_Verb:
1672 tmp.lineTo(pts[1]);
1673 break;
1674 case kQuad_Verb:
1675 subdivide_quad_to(&tmp, pts);
1676 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001677 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001678 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001679 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1680 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 case kCubic_Verb:
1682 subdivide_cubic_to(&tmp, pts);
1683 break;
1684 case kClose_Verb:
1685 tmp.close();
1686 break;
1687 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001688 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 break;
1690 }
1691 }
1692
1693 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001694 SkPathRef::Editor ed(&dst->fPathRef);
1695 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001696 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001698 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1699
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001702 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001704
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001705 if (kUnknown_Direction == fDirection) {
1706 dst->fDirection = kUnknown_Direction;
1707 } else {
1708 SkScalar det2x2 =
1709 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1710 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1711 if (det2x2 < 0) {
1712 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1713 } else if (det2x2 > 0) {
1714 dst->fDirection = fDirection;
1715 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001716 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001717 dst->fDirection = kUnknown_Direction;
1718 }
1719 }
1720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 SkDEBUGCODE(dst->validate();)
1722 }
1723}
1724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725///////////////////////////////////////////////////////////////////////////////
1726///////////////////////////////////////////////////////////////////////////////
1727
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 kEmptyContour_SegmentState, // The current contour is empty. We may be
1730 // starting processing or we may have just
1731 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1733 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1734 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735};
1736
1737SkPath::Iter::Iter() {
1738#ifdef SK_DEBUG
1739 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001740 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001742 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001743 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744#endif
1745 // need to init enough to make next() harmlessly return kDone_Verb
1746 fVerbs = NULL;
1747 fVerbStop = NULL;
1748 fNeedClose = false;
1749}
1750
1751SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1752 this->setPath(path, forceClose);
1753}
1754
1755void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001756 fPts = path.fPathRef->points();
1757 fVerbs = path.fPathRef->verbs();
1758 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001759 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001761 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 fForceClose = SkToU8(forceClose);
1763 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001764 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765}
1766
1767bool SkPath::Iter::isClosedContour() const {
1768 if (fVerbs == NULL || fVerbs == fVerbStop) {
1769 return false;
1770 }
1771 if (fForceClose) {
1772 return true;
1773 }
1774
1775 const uint8_t* verbs = fVerbs;
1776 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 if (kMove_Verb == *(verbs - 1)) {
1779 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 }
1781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 while (verbs > stop) {
1783 // verbs points one beyond the current verb, decrement first.
1784 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 if (kMove_Verb == v) {
1786 break;
1787 }
1788 if (kClose_Verb == v) {
1789 return true;
1790 }
1791 }
1792 return false;
1793}
1794
1795SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001796 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001798 // A special case: if both points are NaN, SkPoint::operation== returns
1799 // false, but the iterator expects that they are treated as the same.
1800 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001801 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1802 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001803 return kClose_Verb;
1804 }
1805
reed@google.com9e25dbf2012-05-15 17:05:38 +00001806 pts[0] = fLastPt;
1807 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 fLastPt = fMoveTo;
1809 fCloseLine = true;
1810 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001811 } else {
1812 pts[0] = fMoveTo;
1813 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815}
1816
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001818 if (fSegmentState == kAfterMove_SegmentState) {
1819 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1824 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001825 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827}
1828
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829void SkPath::Iter::consumeDegenerateSegments() {
1830 // We need to step over anything that will not move the current draw point
1831 // forward before the next move is seen
1832 const uint8_t* lastMoveVerb = 0;
1833 const SkPoint* lastMovePt = 0;
1834 SkPoint lastPt = fLastPt;
1835 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 switch (verb) {
1838 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 // Keep a record of this most recent move
1840 lastMoveVerb = fVerbs;
1841 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001842 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fPts++;
1845 break;
1846
1847 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001848 // A close when we are in a segment is always valid except when it
1849 // follows a move which follows a segment.
1850 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 return;
1852 }
1853 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001854 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 break;
1856
1857 case kLine_Verb:
1858 if (!IsLineDegenerate(lastPt, fPts[0])) {
1859 if (lastMoveVerb) {
1860 fVerbs = lastMoveVerb;
1861 fPts = lastMovePt;
1862 return;
1863 }
1864 return;
1865 }
1866 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fPts++;
1869 break;
1870
reed@google.com277c3f82013-05-31 15:17:50 +00001871 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 case kQuad_Verb:
1873 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1874 if (lastMoveVerb) {
1875 fVerbs = lastMoveVerb;
1876 fPts = lastMovePt;
1877 return;
1878 }
1879 return;
1880 }
1881 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001884 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 break;
1886
1887 case kCubic_Verb:
1888 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1889 if (lastMoveVerb) {
1890 fVerbs = lastMoveVerb;
1891 fPts = lastMovePt;
1892 return;
1893 }
1894 return;
1895 }
1896 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fPts += 3;
1899 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001900
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001902 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 }
1904 }
1905}
1906
reed@google.com4a3b7142012-05-16 17:16:46 +00001907SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 // Close the curve if requested and if there is some curve to close
1912 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 return kLine_Verb;
1915 }
1916 fNeedClose = false;
1917 return kClose_Verb;
1918 }
1919 return kDone_Verb;
1920 }
1921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 // fVerbs is one beyond the current verb, decrement first
1923 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 const SkPoint* SK_RESTRICT srcPts = fPts;
1925 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926
1927 switch (verb) {
1928 case kMove_Verb:
1929 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 verb = this->autoClose(pts);
1932 if (verb == kClose_Verb) {
1933 fNeedClose = false;
1934 }
1935 return (Verb)verb;
1936 }
1937 if (fVerbs == fVerbStop) { // might be a trailing moveto
1938 return kDone_Verb;
1939 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001940 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001941 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001943 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 fNeedClose = fForceClose;
1946 break;
1947 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = this->cons_moveTo();
1949 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 fLastPt = srcPts[0];
1951 fCloseLine = false;
1952 srcPts += 1;
1953 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001954 case kConic_Verb:
1955 fConicWeights += 1;
1956 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001958 pts[0] = this->cons_moveTo();
1959 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 fLastPt = srcPts[1];
1961 srcPts += 2;
1962 break;
1963 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001964 pts[0] = this->cons_moveTo();
1965 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 fLastPt = srcPts[2];
1967 srcPts += 3;
1968 break;
1969 case kClose_Verb:
1970 verb = this->autoClose(pts);
1971 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 } else {
1974 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001975 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 break;
1979 }
1980 fPts = srcPts;
1981 return (Verb)verb;
1982}
1983
1984///////////////////////////////////////////////////////////////////////////////
1985
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001986SkPath::RawIter::RawIter() {
1987#ifdef SK_DEBUG
1988 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001989 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1991#endif
1992 // need to init enough to make next() harmlessly return kDone_Verb
1993 fVerbs = NULL;
1994 fVerbStop = NULL;
1995}
1996
1997SkPath::RawIter::RawIter(const SkPath& path) {
1998 this->setPath(path);
1999}
2000
2001void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002002 fPts = path.fPathRef->points();
2003 fVerbs = path.fPathRef->verbs();
2004 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002005 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006 fMoveTo.fX = fMoveTo.fY = 0;
2007 fLastPt.fX = fLastPt.fY = 0;
2008}
2009
2010SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002011 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012 if (fVerbs == fVerbStop) {
2013 return kDone_Verb;
2014 }
2015
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002016 // fVerbs points one beyond next verb so decrement first.
2017 unsigned verb = *(--fVerbs);
2018 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019
2020 switch (verb) {
2021 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002022 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002023 fMoveTo = srcPts[0];
2024 fLastPt = fMoveTo;
2025 srcPts += 1;
2026 break;
2027 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002028 pts[0] = fLastPt;
2029 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 fLastPt = srcPts[0];
2031 srcPts += 1;
2032 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002033 case kConic_Verb:
2034 fConicWeights += 1;
2035 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002036 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002037 pts[0] = fLastPt;
2038 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002039 fLastPt = srcPts[1];
2040 srcPts += 2;
2041 break;
2042 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002043 pts[0] = fLastPt;
2044 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045 fLastPt = srcPts[2];
2046 srcPts += 3;
2047 break;
2048 case kClose_Verb:
2049 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002050 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002051 break;
2052 }
2053 fPts = srcPts;
2054 return (Verb)verb;
2055}
2056
2057///////////////////////////////////////////////////////////////////////////////
2058
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002060 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061*/
2062
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002063size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 SkDEBUGCODE(this->validate();)
2065
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002066 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002067 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002068 return SkAlign4(byteCount);
2069 }
2070
2071 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002072
robertphillips@google.com466310d2013-12-03 16:43:54 +00002073 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002074 (fFillType << kFillType_SerializationShift) |
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002075 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002076
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002077 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002078
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002079 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002080
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002081 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002082 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083}
2084
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002085size_t SkPath::readFromMemory(const void* storage, size_t length) {
2086 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002087
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002088 int32_t packed;
2089 if (!buffer.readS32(&packed)) {
2090 return 0;
2091 }
2092
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002093 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2094 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002095 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002096 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002097
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002098 size_t sizeRead = 0;
2099 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002100 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002101 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002102 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002103 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002104 } else if (NULL != pathRef) {
2105 // If the buffer is not valid, pathRef should be NULL
2106 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002107 }
2108 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109}
2110
2111///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112
reed@google.com51bbe752013-01-17 22:07:50 +00002113#include "SkString.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002114#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002115
2116static void append_scalar(SkString* str, SkScalar value) {
2117 SkString tmp;
2118 tmp.printf("%g", value);
2119 if (tmp.contains('.')) {
2120 tmp.appendUnichar('f');
2121 }
2122 str->append(tmp);
2123}
2124
2125static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002126 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002127 str->append(label);
2128 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002129
reed@google.com51bbe752013-01-17 22:07:50 +00002130 const SkScalar* values = &pts[0].fX;
2131 count *= 2;
2132
2133 for (int i = 0; i < count; ++i) {
2134 append_scalar(str, values[i]);
2135 if (i < count - 1) {
2136 str->append(", ");
2137 }
2138 }
reed@google.com277c3f82013-05-31 15:17:50 +00002139 if (conicWeight >= 0) {
2140 str->append(", ");
2141 append_scalar(str, conicWeight);
2142 }
reed@google.com51bbe752013-01-17 22:07:50 +00002143 str->append(");\n");
2144}
2145
caryclark66a5d8b2014-06-24 08:30:15 -07002146void SkPath::dump(SkWStream* wStream, bool forceClose) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 Iter iter(*this, forceClose);
2148 SkPoint pts[4];
2149 Verb verb;
2150
caryclark66a5d8b2014-06-24 08:30:15 -07002151 if (!wStream) {
2152 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2153 }
reed@google.com51bbe752013-01-17 22:07:50 +00002154 SkString builder;
2155
reed@google.com4a3b7142012-05-16 17:16:46 +00002156 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 switch (verb) {
2158 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002159 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 break;
2161 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002162 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002165 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002167 case kConic_Verb:
2168 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2169 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002171 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 break;
2173 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002174 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 break;
2176 default:
2177 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2178 verb = kDone_Verb; // stop the loop
2179 break;
2180 }
2181 }
caryclark66a5d8b2014-06-24 08:30:15 -07002182 if (wStream) {
2183 wStream->writeText(builder.c_str());
2184 } else {
2185 SkDebugf("%s", builder.c_str());
2186 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187}
2188
reed@android.come522ca52009-11-23 20:10:41 +00002189void SkPath::dump() const {
caryclark66a5d8b2014-06-24 08:30:15 -07002190 this->dump(NULL, false);
reed@android.come522ca52009-11-23 20:10:41 +00002191}
2192
2193#ifdef SK_DEBUG
2194void SkPath::validate() const {
2195 SkASSERT(this != NULL);
2196 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002197
djsollen@google.com077348c2012-10-22 20:23:32 +00002198#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002199 if (!fBoundsIsDirty) {
2200 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002201
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002202 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002203 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002204
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002205 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002206 // if we're empty, fBounds may be empty but translated, so we can't
2207 // necessarily compare to bounds directly
2208 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2209 // be [2, 2, 2, 2]
2210 SkASSERT(bounds.isEmpty());
2211 SkASSERT(fBounds.isEmpty());
2212 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002213 if (bounds.isEmpty()) {
2214 SkASSERT(fBounds.isEmpty());
2215 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 if (!fBounds.isEmpty()) {
2217 SkASSERT(fBounds.contains(bounds));
2218 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002219 }
reed@android.come522ca52009-11-23 20:10:41 +00002220 }
2221 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002222#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002223}
djsollen@google.com077348c2012-10-22 20:23:32 +00002224#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002225
reed@google.com04863fa2011-05-15 04:08:24 +00002226///////////////////////////////////////////////////////////////////////////////
2227
reed@google.com0b7b9822011-05-16 12:29:27 +00002228static int sign(SkScalar x) { return x < 0; }
2229#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002230
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002231static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002232 // The error epsilon was empirically derived; worse case round rects
2233 // with a mid point outset by 2x float epsilon in tests had an error
2234 // of 12.
2235 const int epsilon = 16;
2236 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2237 return false;
2238 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002239 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002240 int aBits = SkFloatAs2sCompliment(compA);
2241 int bBits = SkFloatAs2sCompliment(compB);
2242 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002243}
2244
2245// only valid for a single contour
2246struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002247 Convexicator()
2248 : fPtCount(0)
2249 , fConvexity(SkPath::kConvex_Convexity)
2250 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002251 fSign = 0;
2252 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002253 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002254 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002255 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002256 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002257
2258 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002259 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002260 }
2261
2262 SkPath::Convexity getConvexity() const { return fConvexity; }
2263
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002264 /** The direction returned is only valid if the path is determined convex */
2265 SkPath::Direction getDirection() const { return fDirection; }
2266
reed@google.com04863fa2011-05-15 04:08:24 +00002267 void addPt(const SkPoint& pt) {
2268 if (SkPath::kConcave_Convexity == fConvexity) {
2269 return;
2270 }
2271
2272 if (0 == fPtCount) {
2273 fCurrPt = pt;
2274 ++fPtCount;
2275 } else {
2276 SkVector vec = pt - fCurrPt;
2277 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002278 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002279 fCurrPt = pt;
2280 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002281 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002282 } else {
2283 SkASSERT(fPtCount > 2);
2284 this->addVec(vec);
2285 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002286
reed@google.com85b6e392011-05-15 20:25:17 +00002287 int sx = sign(vec.fX);
2288 int sy = sign(vec.fY);
2289 fDx += (sx != fSx);
2290 fDy += (sy != fSy);
2291 fSx = sx;
2292 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002293
reed@google.com85b6e392011-05-15 20:25:17 +00002294 if (fDx > 3 || fDy > 3) {
2295 fConvexity = SkPath::kConcave_Convexity;
2296 }
reed@google.com04863fa2011-05-15 04:08:24 +00002297 }
2298 }
2299 }
2300
2301 void close() {
2302 if (fPtCount > 2) {
2303 this->addVec(fFirstVec);
2304 }
2305 }
2306
2307private:
2308 void addVec(const SkVector& vec) {
2309 SkASSERT(vec.fX || vec.fY);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002310 SkScalar cross = SkPoint::CrossProduct(fLastVec, vec);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002311 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2312 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2313 largest = SkTMax(largest, -smallest);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002314 if (!almost_equal(largest, largest + cross)) {
2315 int sign = SkScalarSignAsInt(cross);
2316 if (0 == fSign) {
2317 fSign = sign;
2318 fDirection = (1 == sign) ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2319 } else if (sign && fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002320 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002321 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002322 }
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002323 fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002324 }
2325 }
2326
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002327 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002328 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002329 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2330 // value with the current vec is deemed to be of a significant value.
2331 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002332 int fPtCount; // non-degenerate points
2333 int fSign;
2334 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002335 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002336 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002337};
2338
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002339SkPath::Convexity SkPath::internalGetConvexity() const {
2340 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002341 SkPoint pts[4];
2342 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002343 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002344
2345 int contourCount = 0;
2346 int count;
2347 Convexicator state;
2348
2349 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2350 switch (verb) {
2351 case kMove_Verb:
2352 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002353 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002354 return kConcave_Convexity;
2355 }
2356 pts[1] = pts[0];
2357 count = 1;
2358 break;
2359 case kLine_Verb: count = 1; break;
2360 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002361 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002362 case kCubic_Verb: count = 3; break;
2363 case kClose_Verb:
2364 state.close();
2365 count = 0;
2366 break;
2367 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002368 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002369 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 return kConcave_Convexity;
2371 }
2372
2373 for (int i = 1; i <= count; i++) {
2374 state.addPt(pts[i]);
2375 }
2376 // early exit
2377 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002378 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002379 return kConcave_Convexity;
2380 }
2381 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002382 fConvexity = state.getConvexity();
2383 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2384 fDirection = state.getDirection();
2385 }
2386 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002387}
reed@google.com69a99432012-01-10 18:00:10 +00002388
2389///////////////////////////////////////////////////////////////////////////////
2390
2391class ContourIter {
2392public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002393 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002394
2395 bool done() const { return fDone; }
2396 // if !done() then these may be called
2397 int count() const { return fCurrPtCount; }
2398 const SkPoint* pts() const { return fCurrPt; }
2399 void next();
2400
2401private:
2402 int fCurrPtCount;
2403 const SkPoint* fCurrPt;
2404 const uint8_t* fCurrVerb;
2405 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002406 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002407 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002408 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002409};
2410
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002411ContourIter::ContourIter(const SkPathRef& pathRef) {
2412 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002413 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002414 fCurrPt = pathRef.points();
2415 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002416 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002417 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002418 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002419 this->next();
2420}
2421
2422void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002423 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002424 fDone = true;
2425 }
2426 if (fDone) {
2427 return;
2428 }
2429
2430 // skip pts of prev contour
2431 fCurrPt += fCurrPtCount;
2432
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002433 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002434 int ptCount = 1; // moveTo
2435 const uint8_t* verbs = fCurrVerb;
2436
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002437 for (--verbs; verbs > fStopVerbs; --verbs) {
2438 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002439 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002440 goto CONTOUR_END;
2441 case SkPath::kLine_Verb:
2442 ptCount += 1;
2443 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002444 case SkPath::kConic_Verb:
2445 fCurrConicWeight += 1;
2446 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002447 case SkPath::kQuad_Verb:
2448 ptCount += 2;
2449 break;
2450 case SkPath::kCubic_Verb:
2451 ptCount += 3;
2452 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002453 case SkPath::kClose_Verb:
2454 break;
2455 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002456 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002457 break;
2458 }
2459 }
2460CONTOUR_END:
2461 fCurrPtCount = ptCount;
2462 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002463 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002464}
2465
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002466// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002467static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002468 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2469 // We may get 0 when the above subtracts underflow. We expect this to be
2470 // very rare and lazily promote to double.
2471 if (0 == cross) {
2472 double p0x = SkScalarToDouble(p0.fX);
2473 double p0y = SkScalarToDouble(p0.fY);
2474
2475 double p1x = SkScalarToDouble(p1.fX);
2476 double p1y = SkScalarToDouble(p1.fY);
2477
2478 double p2x = SkScalarToDouble(p2.fX);
2479 double p2y = SkScalarToDouble(p2.fY);
2480
2481 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2482 (p1y - p0y) * (p2x - p0x));
2483
2484 }
2485 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002486}
2487
reed@google.comc1ea60a2012-01-31 15:15:36 +00002488// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002489static int find_max_y(const SkPoint pts[], int count) {
2490 SkASSERT(count > 0);
2491 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002492 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002493 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002494 SkScalar y = pts[i].fY;
2495 if (y > max) {
2496 max = y;
2497 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002498 }
2499 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002500 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002501}
2502
reed@google.comcabaf1d2012-01-11 21:03:05 +00002503static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2504 int i = index;
2505 for (;;) {
2506 i = (i + inc) % n;
2507 if (i == index) { // we wrapped around, so abort
2508 break;
2509 }
2510 if (pts[index] != pts[i]) { // found a different point, success!
2511 break;
2512 }
2513 }
2514 return i;
2515}
2516
reed@google.comc1ea60a2012-01-31 15:15:36 +00002517/**
2518 * Starting at index, and moving forward (incrementing), find the xmin and
2519 * xmax of the contiguous points that have the same Y.
2520 */
2521static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2522 int* maxIndexPtr) {
2523 const SkScalar y = pts[index].fY;
2524 SkScalar min = pts[index].fX;
2525 SkScalar max = min;
2526 int minIndex = index;
2527 int maxIndex = index;
2528 for (int i = index + 1; i < n; ++i) {
2529 if (pts[i].fY != y) {
2530 break;
2531 }
2532 SkScalar x = pts[i].fX;
2533 if (x < min) {
2534 min = x;
2535 minIndex = i;
2536 } else if (x > max) {
2537 max = x;
2538 maxIndex = i;
2539 }
2540 }
2541 *maxIndexPtr = maxIndex;
2542 return minIndex;
2543}
2544
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002545static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002546 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002547}
2548
reed@google.comac8543f2012-01-30 20:51:25 +00002549/*
2550 * We loop through all contours, and keep the computed cross-product of the
2551 * contour that contained the global y-max. If we just look at the first
2552 * contour, we may find one that is wound the opposite way (correctly) since
2553 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2554 * that is outer most (or at least has the global y-max) before we can consider
2555 * its cross product.
2556 */
reed@google.com69a99432012-01-10 18:00:10 +00002557bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002558 if (kUnknown_Direction != fDirection) {
2559 *dir = static_cast<Direction>(fDirection);
2560 return true;
2561 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002562
2563 // don't want to pay the cost for computing this if it
2564 // is unknown, so we don't call isConvex()
2565 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2566 SkASSERT(kUnknown_Direction == fDirection);
2567 *dir = static_cast<Direction>(fDirection);
2568 return false;
2569 }
reed@google.com69a99432012-01-10 18:00:10 +00002570
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002571 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002572
reed@google.comac8543f2012-01-30 20:51:25 +00002573 // initialize with our logical y-min
2574 SkScalar ymax = this->getBounds().fTop;
2575 SkScalar ymaxCross = 0;
2576
reed@google.com69a99432012-01-10 18:00:10 +00002577 for (; !iter.done(); iter.next()) {
2578 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002579 if (n < 3) {
2580 continue;
2581 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002582
reed@google.comcabaf1d2012-01-11 21:03:05 +00002583 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002584 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002585 int index = find_max_y(pts, n);
2586 if (pts[index].fY < ymax) {
2587 continue;
2588 }
2589
2590 // If there is more than 1 distinct point at the y-max, we take the
2591 // x-min and x-max of them and just subtract to compute the dir.
2592 if (pts[(index + 1) % n].fY == pts[index].fY) {
2593 int maxIndex;
2594 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2595 if (minIndex == maxIndex) {
2596 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002597 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002598 SkASSERT(pts[minIndex].fY == pts[index].fY);
2599 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2600 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2601 // we just subtract the indices, and let that auto-convert to
2602 // SkScalar, since we just want - or + to signal the direction.
2603 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002604 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002605 TRY_CROSSPROD:
2606 // Find a next and prev index to use for the cross-product test,
2607 // but we try to find pts that form non-zero vectors from pts[index]
2608 //
2609 // Its possible that we can't find two non-degenerate vectors, so
2610 // we have to guard our search (e.g. all the pts could be in the
2611 // same place).
2612
2613 // we pass n - 1 instead of -1 so we don't foul up % operator by
2614 // passing it a negative LH argument.
2615 int prev = find_diff_pt(pts, index, n, n - 1);
2616 if (prev == index) {
2617 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002618 continue;
2619 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002620 int next = find_diff_pt(pts, index, n, 1);
2621 SkASSERT(next != index);
2622 cross = cross_prod(pts[prev], pts[index], pts[next]);
2623 // if we get a zero and the points are horizontal, then we look at the spread in
2624 // x-direction. We really should continue to walk away from the degeneracy until
2625 // there is a divergence.
2626 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2627 // construct the subtract so we get the correct Direction below
2628 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002629 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002630 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002631
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002632 if (cross) {
2633 // record our best guess so far
2634 ymax = pts[index].fY;
2635 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002636 }
2637 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002638 if (ymaxCross) {
2639 crossToDir(ymaxCross, dir);
2640 fDirection = *dir;
2641 return true;
2642 } else {
2643 return false;
2644 }
reed@google.comac8543f2012-01-30 20:51:25 +00002645}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002646
2647///////////////////////////////////////////////////////////////////////////////
2648
2649static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2650 SkScalar D, SkScalar t) {
2651 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2652}
2653
2654static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2655 SkScalar t) {
2656 SkScalar A = c3 + 3*(c1 - c2) - c0;
2657 SkScalar B = 3*(c2 - c1 - c1 + c0);
2658 SkScalar C = 3*(c1 - c0);
2659 SkScalar D = c0;
2660 return eval_cubic_coeff(A, B, C, D, t);
2661}
2662
2663/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2664 t value such that cubic(t) = target
2665 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002666static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002667 SkScalar target, SkScalar* t) {
2668 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2669 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002670
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002671 SkScalar D = c0 - target;
2672 SkScalar A = c3 + 3*(c1 - c2) - c0;
2673 SkScalar B = 3*(c2 - c1 - c1 + c0);
2674 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002675
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002676 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2677 SkScalar minT = 0;
2678 SkScalar maxT = SK_Scalar1;
2679 SkScalar mid;
2680 int i;
2681 for (i = 0; i < 16; i++) {
2682 mid = SkScalarAve(minT, maxT);
2683 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2684 if (delta < 0) {
2685 minT = mid;
2686 delta = -delta;
2687 } else {
2688 maxT = mid;
2689 }
2690 if (delta < TOLERANCE) {
2691 break;
2692 }
2693 }
2694 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002695}
2696
2697template <size_t N> static void find_minmax(const SkPoint pts[],
2698 SkScalar* minPtr, SkScalar* maxPtr) {
2699 SkScalar min, max;
2700 min = max = pts[0].fX;
2701 for (size_t i = 1; i < N; ++i) {
2702 min = SkMinScalar(min, pts[i].fX);
2703 max = SkMaxScalar(max, pts[i].fX);
2704 }
2705 *minPtr = min;
2706 *maxPtr = max;
2707}
2708
2709static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2710 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002711
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002712 int dir = 1;
2713 if (pts[0].fY > pts[3].fY) {
2714 storage[0] = pts[3];
2715 storage[1] = pts[2];
2716 storage[2] = pts[1];
2717 storage[3] = pts[0];
2718 pts = storage;
2719 dir = -1;
2720 }
2721 if (y < pts[0].fY || y >= pts[3].fY) {
2722 return 0;
2723 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002724
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002725 // quickreject or quickaccept
2726 SkScalar min, max;
2727 find_minmax<4>(pts, &min, &max);
2728 if (x < min) {
2729 return 0;
2730 }
2731 if (x > max) {
2732 return dir;
2733 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002734
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002735 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002736 SkScalar t;
2737 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2738 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 +00002739 return xt < x ? dir : 0;
2740}
2741
2742static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2743 SkPoint dst[10];
2744 int n = SkChopCubicAtYExtrema(pts, dst);
2745 int w = 0;
2746 for (int i = 0; i <= n; ++i) {
2747 w += winding_mono_cubic(&dst[i * 3], x, y);
2748 }
2749 return w;
2750}
2751
2752static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2753 SkScalar y0 = pts[0].fY;
2754 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002755
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756 int dir = 1;
2757 if (y0 > y2) {
2758 SkTSwap(y0, y2);
2759 dir = -1;
2760 }
2761 if (y < y0 || y >= y2) {
2762 return 0;
2763 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 // bounds check on X (not required. is it faster?)
2766#if 0
2767 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2768 return 0;
2769 }
2770#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002771
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772 SkScalar roots[2];
2773 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2774 2 * (pts[1].fY - pts[0].fY),
2775 pts[0].fY - y,
2776 roots);
2777 SkASSERT(n <= 1);
2778 SkScalar xt;
2779 if (0 == n) {
2780 SkScalar mid = SkScalarAve(y0, y2);
2781 // Need [0] and [2] if dir == 1
2782 // and [2] and [0] if dir == -1
2783 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2784 } else {
2785 SkScalar t = roots[0];
2786 SkScalar C = pts[0].fX;
2787 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2788 SkScalar B = 2 * (pts[1].fX - C);
2789 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2790 }
2791 return xt < x ? dir : 0;
2792}
2793
2794static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2795 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2796 if (y0 == y1) {
2797 return true;
2798 }
2799 if (y0 < y1) {
2800 return y1 <= y2;
2801 } else {
2802 return y1 >= y2;
2803 }
2804}
2805
2806static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2807 SkPoint dst[5];
2808 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002809
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2811 n = SkChopQuadAtYExtrema(pts, dst);
2812 pts = dst;
2813 }
2814 int w = winding_mono_quad(pts, x, y);
2815 if (n > 0) {
2816 w += winding_mono_quad(&pts[2], x, y);
2817 }
2818 return w;
2819}
2820
2821static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2822 SkScalar x0 = pts[0].fX;
2823 SkScalar y0 = pts[0].fY;
2824 SkScalar x1 = pts[1].fX;
2825 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 int dir = 1;
2830 if (y0 > y1) {
2831 SkTSwap(y0, y1);
2832 dir = -1;
2833 }
2834 if (y < y0 || y >= y1) {
2835 return 0;
2836 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002837
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2839 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 if (SkScalarSignAsInt(cross) == dir) {
2842 dir = 0;
2843 }
2844 return dir;
2845}
2846
reed@google.com4db592c2013-10-30 17:39:43 +00002847static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2848 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2849}
2850
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851bool SkPath::contains(SkScalar x, SkScalar y) const {
2852 bool isInverse = this->isInverseFillType();
2853 if (this->isEmpty()) {
2854 return isInverse;
2855 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002856
reed@google.com4db592c2013-10-30 17:39:43 +00002857 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858 return isInverse;
2859 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002860
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 SkPath::Iter iter(*this, true);
2862 bool done = false;
2863 int w = 0;
2864 do {
2865 SkPoint pts[4];
2866 switch (iter.next(pts, false)) {
2867 case SkPath::kMove_Verb:
2868 case SkPath::kClose_Verb:
2869 break;
2870 case SkPath::kLine_Verb:
2871 w += winding_line(pts, x, y);
2872 break;
2873 case SkPath::kQuad_Verb:
2874 w += winding_quad(pts, x, y);
2875 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002876 case SkPath::kConic_Verb:
2877 SkASSERT(0);
2878 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 case SkPath::kCubic_Verb:
2880 w += winding_cubic(pts, x, y);
2881 break;
2882 case SkPath::kDone_Verb:
2883 done = true;
2884 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002885 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 switch (this->getFillType()) {
2889 case SkPath::kEvenOdd_FillType:
2890 case SkPath::kInverseEvenOdd_FillType:
2891 w &= 1;
2892 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002893 default:
2894 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 }
2896 return SkToBool(w);
2897}