blob: 9f94b726872dd9e84f3f8426f150861096d45df1 [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"
2114
2115static void append_scalar(SkString* str, SkScalar value) {
2116 SkString tmp;
2117 tmp.printf("%g", value);
2118 if (tmp.contains('.')) {
2119 tmp.appendUnichar('f');
2120 }
2121 str->append(tmp);
2122}
2123
2124static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002125 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002126 str->append(label);
2127 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002128
reed@google.com51bbe752013-01-17 22:07:50 +00002129 const SkScalar* values = &pts[0].fX;
2130 count *= 2;
2131
2132 for (int i = 0; i < count; ++i) {
2133 append_scalar(str, values[i]);
2134 if (i < count - 1) {
2135 str->append(", ");
2136 }
2137 }
reed@google.com277c3f82013-05-31 15:17:50 +00002138 if (conicWeight >= 0) {
2139 str->append(", ");
2140 append_scalar(str, conicWeight);
2141 }
reed@google.com51bbe752013-01-17 22:07:50 +00002142 str->append(");\n");
2143}
2144
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145void SkPath::dump(bool forceClose, const char title[]) const {
2146 Iter iter(*this, forceClose);
2147 SkPoint pts[4];
2148 Verb verb;
2149
2150 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2151 title ? title : "");
2152
reed@google.com51bbe752013-01-17 22:07:50 +00002153 SkString builder;
2154
reed@google.com4a3b7142012-05-16 17:16:46 +00002155 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 switch (verb) {
2157 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002158 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 break;
2160 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002161 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 break;
2163 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002164 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002166 case kConic_Verb:
2167 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2168 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002170 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 break;
2172 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002173 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002174 break;
2175 default:
2176 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2177 verb = kDone_Verb; // stop the loop
2178 break;
2179 }
2180 }
reed@google.com51bbe752013-01-17 22:07:50 +00002181 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182}
2183
reed@android.come522ca52009-11-23 20:10:41 +00002184void SkPath::dump() const {
2185 this->dump(false);
2186}
2187
2188#ifdef SK_DEBUG
2189void SkPath::validate() const {
2190 SkASSERT(this != NULL);
2191 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002192
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194 if (!fBoundsIsDirty) {
2195 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002198 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002199
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002200 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002201 // if we're empty, fBounds may be empty but translated, so we can't
2202 // necessarily compare to bounds directly
2203 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2204 // be [2, 2, 2, 2]
2205 SkASSERT(bounds.isEmpty());
2206 SkASSERT(fBounds.isEmpty());
2207 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 if (bounds.isEmpty()) {
2209 SkASSERT(fBounds.isEmpty());
2210 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002211 if (!fBounds.isEmpty()) {
2212 SkASSERT(fBounds.contains(bounds));
2213 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002214 }
reed@android.come522ca52009-11-23 20:10:41 +00002215 }
2216 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002217#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002218}
djsollen@google.com077348c2012-10-22 20:23:32 +00002219#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002220
reed@google.com04863fa2011-05-15 04:08:24 +00002221///////////////////////////////////////////////////////////////////////////////
2222
reed@google.com0b7b9822011-05-16 12:29:27 +00002223static int sign(SkScalar x) { return x < 0; }
2224#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002225
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002226static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2227 // The error epsilon was empirically derived; worse case round rects
2228 // with a mid point outset by 2x float epsilon in tests had an error
2229 // of 12.
2230 const int epsilon = 16;
2231 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2232 return false;
2233 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002234 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002235 int aBits = SkFloatAs2sCompliment(compA);
2236 int bBits = SkFloatAs2sCompliment(compB);
2237 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002238}
2239
2240// only valid for a single contour
2241struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002242 Convexicator()
2243 : fPtCount(0)
2244 , fConvexity(SkPath::kConvex_Convexity)
2245 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002246 fSign = 0;
2247 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002248 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002249 fCurrPt.set(0, 0);
2250 fVec0.set(0, 0);
2251 fVec1.set(0, 0);
2252 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002253
2254 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002255 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002256 }
2257
2258 SkPath::Convexity getConvexity() const { return fConvexity; }
2259
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002260 /** The direction returned is only valid if the path is determined convex */
2261 SkPath::Direction getDirection() const { return fDirection; }
2262
reed@google.com04863fa2011-05-15 04:08:24 +00002263 void addPt(const SkPoint& pt) {
2264 if (SkPath::kConcave_Convexity == fConvexity) {
2265 return;
2266 }
2267
2268 if (0 == fPtCount) {
2269 fCurrPt = pt;
2270 ++fPtCount;
2271 } else {
2272 SkVector vec = pt - fCurrPt;
2273 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002274 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002275 fCurrPt = pt;
2276 if (++fPtCount == 2) {
2277 fFirstVec = fVec1 = vec;
2278 } else {
2279 SkASSERT(fPtCount > 2);
2280 this->addVec(vec);
2281 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002282
reed@google.com85b6e392011-05-15 20:25:17 +00002283 int sx = sign(vec.fX);
2284 int sy = sign(vec.fY);
2285 fDx += (sx != fSx);
2286 fDy += (sy != fSy);
2287 fSx = sx;
2288 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002289
reed@google.com85b6e392011-05-15 20:25:17 +00002290 if (fDx > 3 || fDy > 3) {
2291 fConvexity = SkPath::kConcave_Convexity;
2292 }
reed@google.com04863fa2011-05-15 04:08:24 +00002293 }
2294 }
2295 }
2296
2297 void close() {
2298 if (fPtCount > 2) {
2299 this->addVec(fFirstVec);
2300 }
2301 }
2302
2303private:
2304 void addVec(const SkVector& vec) {
2305 SkASSERT(vec.fX || vec.fY);
2306 fVec0 = fVec1;
2307 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002308 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2309 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2310 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2311 largest = SkTMax(largest, -smallest);
2312 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002313 if (0 == fSign) {
2314 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002315 if (1 == sign) {
2316 fDirection = SkPath::kCW_Direction;
2317 } else if (-1 == sign) {
2318 fDirection = SkPath::kCCW_Direction;
2319 }
reed@google.com04863fa2011-05-15 04:08:24 +00002320 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002321 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002322 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002323 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002324 }
2325 }
2326 }
2327
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002328 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 SkPoint fCurrPt;
2330 SkVector fVec0, fVec1, fFirstVec;
2331 int fPtCount; // non-degenerate points
2332 int fSign;
2333 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002335 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002336};
2337
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338SkPath::Convexity SkPath::internalGetConvexity() const {
2339 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002340 SkPoint pts[4];
2341 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002342 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002343
2344 int contourCount = 0;
2345 int count;
2346 Convexicator state;
2347
2348 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2349 switch (verb) {
2350 case kMove_Verb:
2351 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002352 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002353 return kConcave_Convexity;
2354 }
2355 pts[1] = pts[0];
2356 count = 1;
2357 break;
2358 case kLine_Verb: count = 1; break;
2359 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002360 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002361 case kCubic_Verb: count = 3; break;
2362 case kClose_Verb:
2363 state.close();
2364 count = 0;
2365 break;
2366 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002367 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002368 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002369 return kConcave_Convexity;
2370 }
2371
2372 for (int i = 1; i <= count; i++) {
2373 state.addPt(pts[i]);
2374 }
2375 // early exit
2376 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002377 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 return kConcave_Convexity;
2379 }
2380 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002381 fConvexity = state.getConvexity();
2382 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2383 fDirection = state.getDirection();
2384 }
2385 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002386}
reed@google.com69a99432012-01-10 18:00:10 +00002387
2388///////////////////////////////////////////////////////////////////////////////
2389
2390class ContourIter {
2391public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002392 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002393
2394 bool done() const { return fDone; }
2395 // if !done() then these may be called
2396 int count() const { return fCurrPtCount; }
2397 const SkPoint* pts() const { return fCurrPt; }
2398 void next();
2399
2400private:
2401 int fCurrPtCount;
2402 const SkPoint* fCurrPt;
2403 const uint8_t* fCurrVerb;
2404 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002405 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002406 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002407 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002408};
2409
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002410ContourIter::ContourIter(const SkPathRef& pathRef) {
2411 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002412 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002413 fCurrPt = pathRef.points();
2414 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002415 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002416 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002417 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002418 this->next();
2419}
2420
2421void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002422 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002423 fDone = true;
2424 }
2425 if (fDone) {
2426 return;
2427 }
2428
2429 // skip pts of prev contour
2430 fCurrPt += fCurrPtCount;
2431
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002432 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002433 int ptCount = 1; // moveTo
2434 const uint8_t* verbs = fCurrVerb;
2435
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002436 for (--verbs; verbs > fStopVerbs; --verbs) {
2437 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002438 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002439 goto CONTOUR_END;
2440 case SkPath::kLine_Verb:
2441 ptCount += 1;
2442 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002443 case SkPath::kConic_Verb:
2444 fCurrConicWeight += 1;
2445 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002446 case SkPath::kQuad_Verb:
2447 ptCount += 2;
2448 break;
2449 case SkPath::kCubic_Verb:
2450 ptCount += 3;
2451 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002452 case SkPath::kClose_Verb:
2453 break;
2454 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002455 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002456 break;
2457 }
2458 }
2459CONTOUR_END:
2460 fCurrPtCount = ptCount;
2461 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002462 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002463}
2464
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002465// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002466static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002467 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2468 // We may get 0 when the above subtracts underflow. We expect this to be
2469 // very rare and lazily promote to double.
2470 if (0 == cross) {
2471 double p0x = SkScalarToDouble(p0.fX);
2472 double p0y = SkScalarToDouble(p0.fY);
2473
2474 double p1x = SkScalarToDouble(p1.fX);
2475 double p1y = SkScalarToDouble(p1.fY);
2476
2477 double p2x = SkScalarToDouble(p2.fX);
2478 double p2y = SkScalarToDouble(p2.fY);
2479
2480 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2481 (p1y - p0y) * (p2x - p0x));
2482
2483 }
2484 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002485}
2486
reed@google.comc1ea60a2012-01-31 15:15:36 +00002487// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002488static int find_max_y(const SkPoint pts[], int count) {
2489 SkASSERT(count > 0);
2490 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002491 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002492 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002493 SkScalar y = pts[i].fY;
2494 if (y > max) {
2495 max = y;
2496 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002497 }
2498 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002499 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002500}
2501
reed@google.comcabaf1d2012-01-11 21:03:05 +00002502static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2503 int i = index;
2504 for (;;) {
2505 i = (i + inc) % n;
2506 if (i == index) { // we wrapped around, so abort
2507 break;
2508 }
2509 if (pts[index] != pts[i]) { // found a different point, success!
2510 break;
2511 }
2512 }
2513 return i;
2514}
2515
reed@google.comc1ea60a2012-01-31 15:15:36 +00002516/**
2517 * Starting at index, and moving forward (incrementing), find the xmin and
2518 * xmax of the contiguous points that have the same Y.
2519 */
2520static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2521 int* maxIndexPtr) {
2522 const SkScalar y = pts[index].fY;
2523 SkScalar min = pts[index].fX;
2524 SkScalar max = min;
2525 int minIndex = index;
2526 int maxIndex = index;
2527 for (int i = index + 1; i < n; ++i) {
2528 if (pts[i].fY != y) {
2529 break;
2530 }
2531 SkScalar x = pts[i].fX;
2532 if (x < min) {
2533 min = x;
2534 minIndex = i;
2535 } else if (x > max) {
2536 max = x;
2537 maxIndex = i;
2538 }
2539 }
2540 *maxIndexPtr = maxIndex;
2541 return minIndex;
2542}
2543
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002544static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002545 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002546}
2547
reed@google.comac8543f2012-01-30 20:51:25 +00002548/*
2549 * We loop through all contours, and keep the computed cross-product of the
2550 * contour that contained the global y-max. If we just look at the first
2551 * contour, we may find one that is wound the opposite way (correctly) since
2552 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2553 * that is outer most (or at least has the global y-max) before we can consider
2554 * its cross product.
2555 */
reed@google.com69a99432012-01-10 18:00:10 +00002556bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002557 if (kUnknown_Direction != fDirection) {
2558 *dir = static_cast<Direction>(fDirection);
2559 return true;
2560 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002561
2562 // don't want to pay the cost for computing this if it
2563 // is unknown, so we don't call isConvex()
2564 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2565 SkASSERT(kUnknown_Direction == fDirection);
2566 *dir = static_cast<Direction>(fDirection);
2567 return false;
2568 }
reed@google.com69a99432012-01-10 18:00:10 +00002569
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002570 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002571
reed@google.comac8543f2012-01-30 20:51:25 +00002572 // initialize with our logical y-min
2573 SkScalar ymax = this->getBounds().fTop;
2574 SkScalar ymaxCross = 0;
2575
reed@google.com69a99432012-01-10 18:00:10 +00002576 for (; !iter.done(); iter.next()) {
2577 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002578 if (n < 3) {
2579 continue;
2580 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002581
reed@google.comcabaf1d2012-01-11 21:03:05 +00002582 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002583 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002584 int index = find_max_y(pts, n);
2585 if (pts[index].fY < ymax) {
2586 continue;
2587 }
2588
2589 // If there is more than 1 distinct point at the y-max, we take the
2590 // x-min and x-max of them and just subtract to compute the dir.
2591 if (pts[(index + 1) % n].fY == pts[index].fY) {
2592 int maxIndex;
2593 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2594 if (minIndex == maxIndex) {
2595 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002596 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002597 SkASSERT(pts[minIndex].fY == pts[index].fY);
2598 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2599 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2600 // we just subtract the indices, and let that auto-convert to
2601 // SkScalar, since we just want - or + to signal the direction.
2602 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002603 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002604 TRY_CROSSPROD:
2605 // Find a next and prev index to use for the cross-product test,
2606 // but we try to find pts that form non-zero vectors from pts[index]
2607 //
2608 // Its possible that we can't find two non-degenerate vectors, so
2609 // we have to guard our search (e.g. all the pts could be in the
2610 // same place).
2611
2612 // we pass n - 1 instead of -1 so we don't foul up % operator by
2613 // passing it a negative LH argument.
2614 int prev = find_diff_pt(pts, index, n, n - 1);
2615 if (prev == index) {
2616 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002617 continue;
2618 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002619 int next = find_diff_pt(pts, index, n, 1);
2620 SkASSERT(next != index);
2621 cross = cross_prod(pts[prev], pts[index], pts[next]);
2622 // if we get a zero and the points are horizontal, then we look at the spread in
2623 // x-direction. We really should continue to walk away from the degeneracy until
2624 // there is a divergence.
2625 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2626 // construct the subtract so we get the correct Direction below
2627 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002628 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002629 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002630
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002631 if (cross) {
2632 // record our best guess so far
2633 ymax = pts[index].fY;
2634 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002635 }
2636 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002637 if (ymaxCross) {
2638 crossToDir(ymaxCross, dir);
2639 fDirection = *dir;
2640 return true;
2641 } else {
2642 return false;
2643 }
reed@google.comac8543f2012-01-30 20:51:25 +00002644}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002645
2646///////////////////////////////////////////////////////////////////////////////
2647
2648static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2649 SkScalar D, SkScalar t) {
2650 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2651}
2652
2653static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2654 SkScalar t) {
2655 SkScalar A = c3 + 3*(c1 - c2) - c0;
2656 SkScalar B = 3*(c2 - c1 - c1 + c0);
2657 SkScalar C = 3*(c1 - c0);
2658 SkScalar D = c0;
2659 return eval_cubic_coeff(A, B, C, D, t);
2660}
2661
2662/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2663 t value such that cubic(t) = target
2664 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002665static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002666 SkScalar target, SkScalar* t) {
2667 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2668 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002669
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002670 SkScalar D = c0 - target;
2671 SkScalar A = c3 + 3*(c1 - c2) - c0;
2672 SkScalar B = 3*(c2 - c1 - c1 + c0);
2673 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002674
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002675 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2676 SkScalar minT = 0;
2677 SkScalar maxT = SK_Scalar1;
2678 SkScalar mid;
2679 int i;
2680 for (i = 0; i < 16; i++) {
2681 mid = SkScalarAve(minT, maxT);
2682 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2683 if (delta < 0) {
2684 minT = mid;
2685 delta = -delta;
2686 } else {
2687 maxT = mid;
2688 }
2689 if (delta < TOLERANCE) {
2690 break;
2691 }
2692 }
2693 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002694}
2695
2696template <size_t N> static void find_minmax(const SkPoint pts[],
2697 SkScalar* minPtr, SkScalar* maxPtr) {
2698 SkScalar min, max;
2699 min = max = pts[0].fX;
2700 for (size_t i = 1; i < N; ++i) {
2701 min = SkMinScalar(min, pts[i].fX);
2702 max = SkMaxScalar(max, pts[i].fX);
2703 }
2704 *minPtr = min;
2705 *maxPtr = max;
2706}
2707
2708static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2709 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002710
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002711 int dir = 1;
2712 if (pts[0].fY > pts[3].fY) {
2713 storage[0] = pts[3];
2714 storage[1] = pts[2];
2715 storage[2] = pts[1];
2716 storage[3] = pts[0];
2717 pts = storage;
2718 dir = -1;
2719 }
2720 if (y < pts[0].fY || y >= pts[3].fY) {
2721 return 0;
2722 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 // quickreject or quickaccept
2725 SkScalar min, max;
2726 find_minmax<4>(pts, &min, &max);
2727 if (x < min) {
2728 return 0;
2729 }
2730 if (x > max) {
2731 return dir;
2732 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002733
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002734 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002735 SkScalar t;
2736 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2737 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 +00002738 return xt < x ? dir : 0;
2739}
2740
2741static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2742 SkPoint dst[10];
2743 int n = SkChopCubicAtYExtrema(pts, dst);
2744 int w = 0;
2745 for (int i = 0; i <= n; ++i) {
2746 w += winding_mono_cubic(&dst[i * 3], x, y);
2747 }
2748 return w;
2749}
2750
2751static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2752 SkScalar y0 = pts[0].fY;
2753 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002754
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755 int dir = 1;
2756 if (y0 > y2) {
2757 SkTSwap(y0, y2);
2758 dir = -1;
2759 }
2760 if (y < y0 || y >= y2) {
2761 return 0;
2762 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002763
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764 // bounds check on X (not required. is it faster?)
2765#if 0
2766 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2767 return 0;
2768 }
2769#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771 SkScalar roots[2];
2772 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2773 2 * (pts[1].fY - pts[0].fY),
2774 pts[0].fY - y,
2775 roots);
2776 SkASSERT(n <= 1);
2777 SkScalar xt;
2778 if (0 == n) {
2779 SkScalar mid = SkScalarAve(y0, y2);
2780 // Need [0] and [2] if dir == 1
2781 // and [2] and [0] if dir == -1
2782 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2783 } else {
2784 SkScalar t = roots[0];
2785 SkScalar C = pts[0].fX;
2786 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2787 SkScalar B = 2 * (pts[1].fX - C);
2788 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2789 }
2790 return xt < x ? dir : 0;
2791}
2792
2793static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2794 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2795 if (y0 == y1) {
2796 return true;
2797 }
2798 if (y0 < y1) {
2799 return y1 <= y2;
2800 } else {
2801 return y1 >= y2;
2802 }
2803}
2804
2805static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2806 SkPoint dst[5];
2807 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2810 n = SkChopQuadAtYExtrema(pts, dst);
2811 pts = dst;
2812 }
2813 int w = winding_mono_quad(pts, x, y);
2814 if (n > 0) {
2815 w += winding_mono_quad(&pts[2], x, y);
2816 }
2817 return w;
2818}
2819
2820static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2821 SkScalar x0 = pts[0].fX;
2822 SkScalar y0 = pts[0].fY;
2823 SkScalar x1 = pts[1].fX;
2824 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002825
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002826 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002827
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 int dir = 1;
2829 if (y0 > y1) {
2830 SkTSwap(y0, y1);
2831 dir = -1;
2832 }
2833 if (y < y0 || y >= y1) {
2834 return 0;
2835 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002836
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2838 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002839
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 if (SkScalarSignAsInt(cross) == dir) {
2841 dir = 0;
2842 }
2843 return dir;
2844}
2845
reed@google.com4db592c2013-10-30 17:39:43 +00002846static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2847 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2848}
2849
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002850bool SkPath::contains(SkScalar x, SkScalar y) const {
2851 bool isInverse = this->isInverseFillType();
2852 if (this->isEmpty()) {
2853 return isInverse;
2854 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002855
reed@google.com4db592c2013-10-30 17:39:43 +00002856 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 return isInverse;
2858 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 SkPath::Iter iter(*this, true);
2861 bool done = false;
2862 int w = 0;
2863 do {
2864 SkPoint pts[4];
2865 switch (iter.next(pts, false)) {
2866 case SkPath::kMove_Verb:
2867 case SkPath::kClose_Verb:
2868 break;
2869 case SkPath::kLine_Verb:
2870 w += winding_line(pts, x, y);
2871 break;
2872 case SkPath::kQuad_Verb:
2873 w += winding_quad(pts, x, y);
2874 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002875 case SkPath::kConic_Verb:
2876 SkASSERT(0);
2877 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 case SkPath::kCubic_Verb:
2879 w += winding_cubic(pts, x, y);
2880 break;
2881 case SkPath::kDone_Verb:
2882 done = true;
2883 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002884 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002886
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002887 switch (this->getFillType()) {
2888 case SkPath::kEvenOdd_FillType:
2889 case SkPath::kInverseEvenOdd_FillType:
2890 w &= 1;
2891 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002892 default:
2893 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 }
2895 return SkToBool(w);
2896}