blob: d51a40e2718e391c0ec288733ac31c74b5401f99 [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.org8be07bb2014-05-22 14:58:53 +00002226static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002227 // 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);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002250 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002251 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002252
2253 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002254 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002255 }
2256
2257 SkPath::Convexity getConvexity() const { return fConvexity; }
2258
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002259 /** The direction returned is only valid if the path is determined convex */
2260 SkPath::Direction getDirection() const { return fDirection; }
2261
reed@google.com04863fa2011-05-15 04:08:24 +00002262 void addPt(const SkPoint& pt) {
2263 if (SkPath::kConcave_Convexity == fConvexity) {
2264 return;
2265 }
2266
2267 if (0 == fPtCount) {
2268 fCurrPt = pt;
2269 ++fPtCount;
2270 } else {
2271 SkVector vec = pt - fCurrPt;
2272 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002273 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002274 fCurrPt = pt;
2275 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002276 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002277 } else {
2278 SkASSERT(fPtCount > 2);
2279 this->addVec(vec);
2280 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002281
reed@google.com85b6e392011-05-15 20:25:17 +00002282 int sx = sign(vec.fX);
2283 int sy = sign(vec.fY);
2284 fDx += (sx != fSx);
2285 fDy += (sy != fSy);
2286 fSx = sx;
2287 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002288
reed@google.com85b6e392011-05-15 20:25:17 +00002289 if (fDx > 3 || fDy > 3) {
2290 fConvexity = SkPath::kConcave_Convexity;
2291 }
reed@google.com04863fa2011-05-15 04:08:24 +00002292 }
2293 }
2294 }
2295
2296 void close() {
2297 if (fPtCount > 2) {
2298 this->addVec(fFirstVec);
2299 }
2300 }
2301
2302private:
2303 void addVec(const SkVector& vec) {
2304 SkASSERT(vec.fX || vec.fY);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002305 SkScalar cross = SkPoint::CrossProduct(fLastVec, vec);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002306 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2307 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2308 largest = SkTMax(largest, -smallest);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002309 if (!almost_equal(largest, largest + cross)) {
2310 int sign = SkScalarSignAsInt(cross);
2311 if (0 == fSign) {
2312 fSign = sign;
2313 fDirection = (1 == sign) ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2314 } else if (sign && fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002315 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002316 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002317 }
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002318 fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002319 }
2320 }
2321
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002322 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002323 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002324 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2325 // value with the current vec is deemed to be of a significant value.
2326 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002327 int fPtCount; // non-degenerate points
2328 int fSign;
2329 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002331 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002332};
2333
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334SkPath::Convexity SkPath::internalGetConvexity() const {
2335 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002336 SkPoint pts[4];
2337 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002339
2340 int contourCount = 0;
2341 int count;
2342 Convexicator state;
2343
2344 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2345 switch (verb) {
2346 case kMove_Verb:
2347 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002348 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002349 return kConcave_Convexity;
2350 }
2351 pts[1] = pts[0];
2352 count = 1;
2353 break;
2354 case kLine_Verb: count = 1; break;
2355 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002356 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002357 case kCubic_Verb: count = 3; break;
2358 case kClose_Verb:
2359 state.close();
2360 count = 0;
2361 break;
2362 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002363 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002364 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002365 return kConcave_Convexity;
2366 }
2367
2368 for (int i = 1; i <= count; i++) {
2369 state.addPt(pts[i]);
2370 }
2371 // early exit
2372 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002373 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002374 return kConcave_Convexity;
2375 }
2376 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002377 fConvexity = state.getConvexity();
2378 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2379 fDirection = state.getDirection();
2380 }
2381 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002382}
reed@google.com69a99432012-01-10 18:00:10 +00002383
2384///////////////////////////////////////////////////////////////////////////////
2385
2386class ContourIter {
2387public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002388 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002389
2390 bool done() const { return fDone; }
2391 // if !done() then these may be called
2392 int count() const { return fCurrPtCount; }
2393 const SkPoint* pts() const { return fCurrPt; }
2394 void next();
2395
2396private:
2397 int fCurrPtCount;
2398 const SkPoint* fCurrPt;
2399 const uint8_t* fCurrVerb;
2400 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002401 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002402 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002403 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002404};
2405
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002406ContourIter::ContourIter(const SkPathRef& pathRef) {
2407 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002408 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002409 fCurrPt = pathRef.points();
2410 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002411 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002412 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002413 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002414 this->next();
2415}
2416
2417void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002418 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002419 fDone = true;
2420 }
2421 if (fDone) {
2422 return;
2423 }
2424
2425 // skip pts of prev contour
2426 fCurrPt += fCurrPtCount;
2427
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002428 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002429 int ptCount = 1; // moveTo
2430 const uint8_t* verbs = fCurrVerb;
2431
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002432 for (--verbs; verbs > fStopVerbs; --verbs) {
2433 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002434 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002435 goto CONTOUR_END;
2436 case SkPath::kLine_Verb:
2437 ptCount += 1;
2438 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002439 case SkPath::kConic_Verb:
2440 fCurrConicWeight += 1;
2441 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002442 case SkPath::kQuad_Verb:
2443 ptCount += 2;
2444 break;
2445 case SkPath::kCubic_Verb:
2446 ptCount += 3;
2447 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002448 case SkPath::kClose_Verb:
2449 break;
2450 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002451 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002452 break;
2453 }
2454 }
2455CONTOUR_END:
2456 fCurrPtCount = ptCount;
2457 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002458 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002459}
2460
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002461// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002462static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002463 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2464 // We may get 0 when the above subtracts underflow. We expect this to be
2465 // very rare and lazily promote to double.
2466 if (0 == cross) {
2467 double p0x = SkScalarToDouble(p0.fX);
2468 double p0y = SkScalarToDouble(p0.fY);
2469
2470 double p1x = SkScalarToDouble(p1.fX);
2471 double p1y = SkScalarToDouble(p1.fY);
2472
2473 double p2x = SkScalarToDouble(p2.fX);
2474 double p2y = SkScalarToDouble(p2.fY);
2475
2476 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2477 (p1y - p0y) * (p2x - p0x));
2478
2479 }
2480 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002481}
2482
reed@google.comc1ea60a2012-01-31 15:15:36 +00002483// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002484static int find_max_y(const SkPoint pts[], int count) {
2485 SkASSERT(count > 0);
2486 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002487 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002488 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002489 SkScalar y = pts[i].fY;
2490 if (y > max) {
2491 max = y;
2492 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002493 }
2494 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002495 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002496}
2497
reed@google.comcabaf1d2012-01-11 21:03:05 +00002498static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2499 int i = index;
2500 for (;;) {
2501 i = (i + inc) % n;
2502 if (i == index) { // we wrapped around, so abort
2503 break;
2504 }
2505 if (pts[index] != pts[i]) { // found a different point, success!
2506 break;
2507 }
2508 }
2509 return i;
2510}
2511
reed@google.comc1ea60a2012-01-31 15:15:36 +00002512/**
2513 * Starting at index, and moving forward (incrementing), find the xmin and
2514 * xmax of the contiguous points that have the same Y.
2515 */
2516static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2517 int* maxIndexPtr) {
2518 const SkScalar y = pts[index].fY;
2519 SkScalar min = pts[index].fX;
2520 SkScalar max = min;
2521 int minIndex = index;
2522 int maxIndex = index;
2523 for (int i = index + 1; i < n; ++i) {
2524 if (pts[i].fY != y) {
2525 break;
2526 }
2527 SkScalar x = pts[i].fX;
2528 if (x < min) {
2529 min = x;
2530 minIndex = i;
2531 } else if (x > max) {
2532 max = x;
2533 maxIndex = i;
2534 }
2535 }
2536 *maxIndexPtr = maxIndex;
2537 return minIndex;
2538}
2539
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002540static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002541 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002542}
2543
reed@google.comac8543f2012-01-30 20:51:25 +00002544/*
2545 * We loop through all contours, and keep the computed cross-product of the
2546 * contour that contained the global y-max. If we just look at the first
2547 * contour, we may find one that is wound the opposite way (correctly) since
2548 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2549 * that is outer most (or at least has the global y-max) before we can consider
2550 * its cross product.
2551 */
reed@google.com69a99432012-01-10 18:00:10 +00002552bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002553 if (kUnknown_Direction != fDirection) {
2554 *dir = static_cast<Direction>(fDirection);
2555 return true;
2556 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002557
2558 // don't want to pay the cost for computing this if it
2559 // is unknown, so we don't call isConvex()
2560 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2561 SkASSERT(kUnknown_Direction == fDirection);
2562 *dir = static_cast<Direction>(fDirection);
2563 return false;
2564 }
reed@google.com69a99432012-01-10 18:00:10 +00002565
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002566 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002567
reed@google.comac8543f2012-01-30 20:51:25 +00002568 // initialize with our logical y-min
2569 SkScalar ymax = this->getBounds().fTop;
2570 SkScalar ymaxCross = 0;
2571
reed@google.com69a99432012-01-10 18:00:10 +00002572 for (; !iter.done(); iter.next()) {
2573 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002574 if (n < 3) {
2575 continue;
2576 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002577
reed@google.comcabaf1d2012-01-11 21:03:05 +00002578 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002579 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002580 int index = find_max_y(pts, n);
2581 if (pts[index].fY < ymax) {
2582 continue;
2583 }
2584
2585 // If there is more than 1 distinct point at the y-max, we take the
2586 // x-min and x-max of them and just subtract to compute the dir.
2587 if (pts[(index + 1) % n].fY == pts[index].fY) {
2588 int maxIndex;
2589 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2590 if (minIndex == maxIndex) {
2591 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002592 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002593 SkASSERT(pts[minIndex].fY == pts[index].fY);
2594 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2595 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2596 // we just subtract the indices, and let that auto-convert to
2597 // SkScalar, since we just want - or + to signal the direction.
2598 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002599 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002600 TRY_CROSSPROD:
2601 // Find a next and prev index to use for the cross-product test,
2602 // but we try to find pts that form non-zero vectors from pts[index]
2603 //
2604 // Its possible that we can't find two non-degenerate vectors, so
2605 // we have to guard our search (e.g. all the pts could be in the
2606 // same place).
2607
2608 // we pass n - 1 instead of -1 so we don't foul up % operator by
2609 // passing it a negative LH argument.
2610 int prev = find_diff_pt(pts, index, n, n - 1);
2611 if (prev == index) {
2612 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002613 continue;
2614 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002615 int next = find_diff_pt(pts, index, n, 1);
2616 SkASSERT(next != index);
2617 cross = cross_prod(pts[prev], pts[index], pts[next]);
2618 // if we get a zero and the points are horizontal, then we look at the spread in
2619 // x-direction. We really should continue to walk away from the degeneracy until
2620 // there is a divergence.
2621 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2622 // construct the subtract so we get the correct Direction below
2623 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002624 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002625 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002626
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002627 if (cross) {
2628 // record our best guess so far
2629 ymax = pts[index].fY;
2630 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002631 }
2632 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002633 if (ymaxCross) {
2634 crossToDir(ymaxCross, dir);
2635 fDirection = *dir;
2636 return true;
2637 } else {
2638 return false;
2639 }
reed@google.comac8543f2012-01-30 20:51:25 +00002640}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002641
2642///////////////////////////////////////////////////////////////////////////////
2643
2644static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2645 SkScalar D, SkScalar t) {
2646 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2647}
2648
2649static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2650 SkScalar t) {
2651 SkScalar A = c3 + 3*(c1 - c2) - c0;
2652 SkScalar B = 3*(c2 - c1 - c1 + c0);
2653 SkScalar C = 3*(c1 - c0);
2654 SkScalar D = c0;
2655 return eval_cubic_coeff(A, B, C, D, t);
2656}
2657
2658/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2659 t value such that cubic(t) = target
2660 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002661static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002662 SkScalar target, SkScalar* t) {
2663 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2664 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002665
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002666 SkScalar D = c0 - target;
2667 SkScalar A = c3 + 3*(c1 - c2) - c0;
2668 SkScalar B = 3*(c2 - c1 - c1 + c0);
2669 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002670
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002671 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2672 SkScalar minT = 0;
2673 SkScalar maxT = SK_Scalar1;
2674 SkScalar mid;
2675 int i;
2676 for (i = 0; i < 16; i++) {
2677 mid = SkScalarAve(minT, maxT);
2678 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2679 if (delta < 0) {
2680 minT = mid;
2681 delta = -delta;
2682 } else {
2683 maxT = mid;
2684 }
2685 if (delta < TOLERANCE) {
2686 break;
2687 }
2688 }
2689 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690}
2691
2692template <size_t N> static void find_minmax(const SkPoint pts[],
2693 SkScalar* minPtr, SkScalar* maxPtr) {
2694 SkScalar min, max;
2695 min = max = pts[0].fX;
2696 for (size_t i = 1; i < N; ++i) {
2697 min = SkMinScalar(min, pts[i].fX);
2698 max = SkMaxScalar(max, pts[i].fX);
2699 }
2700 *minPtr = min;
2701 *maxPtr = max;
2702}
2703
2704static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2705 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002706
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002707 int dir = 1;
2708 if (pts[0].fY > pts[3].fY) {
2709 storage[0] = pts[3];
2710 storage[1] = pts[2];
2711 storage[2] = pts[1];
2712 storage[3] = pts[0];
2713 pts = storage;
2714 dir = -1;
2715 }
2716 if (y < pts[0].fY || y >= pts[3].fY) {
2717 return 0;
2718 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002719
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002720 // quickreject or quickaccept
2721 SkScalar min, max;
2722 find_minmax<4>(pts, &min, &max);
2723 if (x < min) {
2724 return 0;
2725 }
2726 if (x > max) {
2727 return dir;
2728 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002729
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002731 SkScalar t;
2732 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2733 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 +00002734 return xt < x ? dir : 0;
2735}
2736
2737static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2738 SkPoint dst[10];
2739 int n = SkChopCubicAtYExtrema(pts, dst);
2740 int w = 0;
2741 for (int i = 0; i <= n; ++i) {
2742 w += winding_mono_cubic(&dst[i * 3], x, y);
2743 }
2744 return w;
2745}
2746
2747static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2748 SkScalar y0 = pts[0].fY;
2749 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002750
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751 int dir = 1;
2752 if (y0 > y2) {
2753 SkTSwap(y0, y2);
2754 dir = -1;
2755 }
2756 if (y < y0 || y >= y2) {
2757 return 0;
2758 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 // bounds check on X (not required. is it faster?)
2761#if 0
2762 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2763 return 0;
2764 }
2765#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002766
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002767 SkScalar roots[2];
2768 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2769 2 * (pts[1].fY - pts[0].fY),
2770 pts[0].fY - y,
2771 roots);
2772 SkASSERT(n <= 1);
2773 SkScalar xt;
2774 if (0 == n) {
2775 SkScalar mid = SkScalarAve(y0, y2);
2776 // Need [0] and [2] if dir == 1
2777 // and [2] and [0] if dir == -1
2778 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2779 } else {
2780 SkScalar t = roots[0];
2781 SkScalar C = pts[0].fX;
2782 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2783 SkScalar B = 2 * (pts[1].fX - C);
2784 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2785 }
2786 return xt < x ? dir : 0;
2787}
2788
2789static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2790 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2791 if (y0 == y1) {
2792 return true;
2793 }
2794 if (y0 < y1) {
2795 return y1 <= y2;
2796 } else {
2797 return y1 >= y2;
2798 }
2799}
2800
2801static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2802 SkPoint dst[5];
2803 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002804
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2806 n = SkChopQuadAtYExtrema(pts, dst);
2807 pts = dst;
2808 }
2809 int w = winding_mono_quad(pts, x, y);
2810 if (n > 0) {
2811 w += winding_mono_quad(&pts[2], x, y);
2812 }
2813 return w;
2814}
2815
2816static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2817 SkScalar x0 = pts[0].fX;
2818 SkScalar y0 = pts[0].fY;
2819 SkScalar x1 = pts[1].fX;
2820 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 int dir = 1;
2825 if (y0 > y1) {
2826 SkTSwap(y0, y1);
2827 dir = -1;
2828 }
2829 if (y < y0 || y >= y1) {
2830 return 0;
2831 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2834 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 if (SkScalarSignAsInt(cross) == dir) {
2837 dir = 0;
2838 }
2839 return dir;
2840}
2841
reed@google.com4db592c2013-10-30 17:39:43 +00002842static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2843 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2844}
2845
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846bool SkPath::contains(SkScalar x, SkScalar y) const {
2847 bool isInverse = this->isInverseFillType();
2848 if (this->isEmpty()) {
2849 return isInverse;
2850 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
reed@google.com4db592c2013-10-30 17:39:43 +00002852 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 return isInverse;
2854 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002855
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002856 SkPath::Iter iter(*this, true);
2857 bool done = false;
2858 int w = 0;
2859 do {
2860 SkPoint pts[4];
2861 switch (iter.next(pts, false)) {
2862 case SkPath::kMove_Verb:
2863 case SkPath::kClose_Verb:
2864 break;
2865 case SkPath::kLine_Verb:
2866 w += winding_line(pts, x, y);
2867 break;
2868 case SkPath::kQuad_Verb:
2869 w += winding_quad(pts, x, y);
2870 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002871 case SkPath::kConic_Verb:
2872 SkASSERT(0);
2873 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002874 case SkPath::kCubic_Verb:
2875 w += winding_cubic(pts, x, y);
2876 break;
2877 case SkPath::kDone_Verb:
2878 done = true;
2879 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002880 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002882
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002883 switch (this->getFillType()) {
2884 case SkPath::kEvenOdd_FillType:
2885 case SkPath::kInverseEvenOdd_FillType:
2886 w &= 1;
2887 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002888 default:
2889 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 }
2891 return SkToBool(w);
2892}