blob: 6692a0f94ed134b996b0b5ff28cb8a2e7f0bfb03 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
41 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
42 }
43
44 ~SkAutoDisableDirectionCheck() {
45 fPath->fDirection = fSaved;
46 }
47
48private:
49 SkPath* fPath;
50 SkPath::Direction fSaved;
51};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
reed@google.comd335d1d2012-01-12 18:17:11 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000129 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000131 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132#endif
133{
134 this->resetFields();
135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181}
182
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000183bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000184 // note: don't need to look at isConvex or bounds, since just comparing the
185 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000186 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000187 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190void SkPath::swap(SkPath& that) {
191 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 if (this != &that) {
194 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
196 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
198 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000199#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000200 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
201#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000205static inline bool check_edge_against_rect(const SkPoint& p0,
206 const SkPoint& p1,
207 const SkRect& rect,
208 SkPath::Direction dir) {
209 const SkPoint* edgeBegin;
210 SkVector v;
211 if (SkPath::kCW_Direction == dir) {
212 v = p1 - p0;
213 edgeBegin = &p0;
214 } else {
215 v = p0 - p1;
216 edgeBegin = &p1;
217 }
218 if (v.fX || v.fY) {
219 // check the cross product of v with the vec from edgeBegin to each rect corner
220 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
221 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
222 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
223 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
224 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
225 return false;
226 }
227 }
228 return true;
229}
230
231bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
232 // This only handles non-degenerate convex paths currently.
233 if (kConvex_Convexity != this->getConvexity()) {
234 return false;
235 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237 Direction direction;
238 if (!this->cheapComputeDirection(&direction)) {
239 return false;
240 }
241
242 SkPoint firstPt;
243 SkPoint prevPt;
244 RawIter iter(*this);
245 SkPath::Verb verb;
246 SkPoint pts[4];
247 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000248 SkDEBUGCODE(int segmentCount = 0;)
249 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250
251 while ((verb = iter.next(pts)) != kDone_Verb) {
252 int nextPt = -1;
253 switch (verb) {
254 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000256 SkDEBUGCODE(++moveCnt);
257 firstPt = prevPt = pts[0];
258 break;
259 case kLine_Verb:
260 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 break;
264 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000265 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkASSERT(moveCnt && !closeCount);
267 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 nextPt = 2;
269 break;
270 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkASSERT(moveCnt && !closeCount);
272 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273 nextPt = 3;
274 break;
275 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000276 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 break;
278 default:
279 SkDEBUGFAIL("unknown verb");
280 }
281 if (-1 != nextPt) {
282 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
283 return false;
284 }
285 prevPt = pts[nextPt];
286 }
287 }
288
289 return check_edge_against_rect(prevPt, firstPt, rect, direction);
290}
291
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000292uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000293 uint32_t genID = fPathRef->genID();
294#ifdef SK_BUILD_FOR_ANDROID
295 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
296 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
297#endif
298 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000299}
djsollen@google.come63793a2012-03-21 15:39:03 +0000300
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000302const SkPath* SkPath::getSourcePath() const {
303 return fSourcePath;
304}
305
306void SkPath::setSourcePath(const SkPath* path) {
307 fSourcePath = path;
308}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000309#endif
310
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311void SkPath::reset() {
312 SkDEBUGCODE(this->validate();)
313
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000314 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000315 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316}
317
318void SkPath::rewind() {
319 SkDEBUGCODE(this->validate();)
320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000322 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323}
324
reed@google.com7e6c4d12012-05-10 14:05:43 +0000325bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000326 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000327
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000328 if (2 == verbCount) {
329 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
330 if (kLine_Verb == fPathRef->atVerb(1)) {
331 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000332 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000334 line[0] = pts[0];
335 line[1] = pts[1];
336 }
337 return true;
338 }
339 }
340 return false;
341}
342
caryclark@google.comf1316942011-07-26 19:54:45 +0000343/*
344 Determines if path is a rect by keeping track of changes in direction
345 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347 The direction is computed such that:
348 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000349 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000350 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000351 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000352
caryclark@google.comf1316942011-07-26 19:54:45 +0000353A rectangle cycles up/right/down/left or up/left/down/right.
354
355The test fails if:
356 The path is closed, and followed by a line.
357 A second move creates a new endpoint.
358 A diagonal line is parsed.
359 There's more than four changes of direction.
360 There's a discontinuity on the line (e.g., a move in the middle)
361 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000362 The path contains a quadratic or cubic.
363 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000364 *The rectangle doesn't complete a cycle.
365 *The final point isn't equal to the first point.
366
367 *These last two conditions we relax if we have a 3-edge path that would
368 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370It's OK if the path has:
371 Several colinear line segments composing a rectangle side.
372 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374The direction takes advantage of the corners found since opposite sides
375must travel in opposite directions.
376
377FIXME: Allow colinear quads and cubics to be treated like lines.
378FIXME: If the API passes fill-only, return true if the filled stroke
379 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000380
381 first,last,next direction state-machine:
382 0x1 is set if the segment is horizontal
383 0x2 is set if the segment is moving to the right or down
384 thus:
385 two directions are opposites iff (dirA ^ dirB) == 0x2
386 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000389static int rect_make_dir(SkScalar dx, SkScalar dy) {
390 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
391}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
393 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 int corners = 0;
395 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000396 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000397 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000398 first.set(0, 0);
399 last.set(0, 0);
400 int firstDirection = 0;
401 int lastDirection = 0;
402 int nextDirection = 0;
403 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000405 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
407 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 savePts = pts;
410 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 autoClose = true;
412 case kLine_Verb: {
413 SkScalar left = last.fX;
414 SkScalar top = last.fY;
415 SkScalar right = pts->fX;
416 SkScalar bottom = pts->fY;
417 ++pts;
418 if (left != right && top != bottom) {
419 return false; // diagonal
420 }
421 if (left == right && top == bottom) {
422 break; // single point on side OK
423 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 if (0 == corners) {
426 firstDirection = nextDirection;
427 first = last;
428 last = pts[-1];
429 corners = 1;
430 closedOrMoved = false;
431 break;
432 }
433 if (closedOrMoved) {
434 return false; // closed followed by a line
435 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000436 if (autoClose && nextDirection == firstDirection) {
437 break; // colinear with first
438 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 closedOrMoved = autoClose;
440 if (lastDirection != nextDirection) {
441 if (++corners > 4) {
442 return false; // too many direction changes
443 }
444 }
445 last = pts[-1];
446 if (lastDirection == nextDirection) {
447 break; // colinear segment
448 }
449 // Possible values for corners are 2, 3, and 4.
450 // When corners == 3, nextDirection opposes firstDirection.
451 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000452 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
454 if ((directionCycle ^ turn) != nextDirection) {
455 return false; // direction didn't follow cycle
456 }
457 break;
458 }
459 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000460 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 case kCubic_Verb:
462 return false; // quadratic, cubic not allowed
463 case kMove_Verb:
464 last = *pts++;
465 closedOrMoved = true;
466 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000467 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000468 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000469 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 lastDirection = nextDirection;
473 }
474 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000476 if (!result) {
477 // check if we are just an incomplete rectangle, in which case we can
478 // return true, but not claim to be closed.
479 // e.g.
480 // 3 sided rectangle
481 // 4 sided but the last edge is not long enough to reach the start
482 //
483 SkScalar closeX = first.x() - last.x();
484 SkScalar closeY = first.y() - last.y();
485 if (closeX && closeY) {
486 return false; // we're diagonal, abort (can we ever reach this?)
487 }
488 int closeDirection = rect_make_dir(closeX, closeY);
489 // make sure the close-segment doesn't double-back on itself
490 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
491 result = true;
492 autoClose = false; // we are not closed
493 }
494 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000507bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 SkDEBUGCODE(this->validate();)
509 int currVerb = 0;
510 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000511 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
512 if (result && rect) {
513 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515 return result;
516}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000517
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518bool SkPath::isRect(bool* isClosed, Direction* direction) const {
519 SkDEBUGCODE(this->validate();)
520 int currVerb = 0;
521 const SkPoint* pts = fPathRef->points();
522 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000523}
524
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000525bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 SkDEBUGCODE(this->validate();)
527 int currVerb = 0;
528 const SkPoint* pts = fPathRef->points();
529 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000530 Direction testDirs[2];
531 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 return false;
533 }
534 const SkPoint* last = pts;
535 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000537 testRects[0].set(first, SkToS32(last - first));
538 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 if (testRects[0].contains(testRects[1])) {
540 if (rects) {
541 rects[0] = testRects[0];
542 rects[1] = testRects[1];
543 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (dirs) {
545 dirs[0] = testDirs[0];
546 dirs[1] = testDirs[1];
547 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return true;
549 }
550 if (testRects[1].contains(testRects[0])) {
551 if (rects) {
552 rects[0] = testRects[1];
553 rects[1] = testRects[0];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[1];
557 dirs[1] = testDirs[0];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 }
562 return false;
563}
564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000565int SkPath::countPoints() const {
566 return fPathRef->countPoints();
567}
568
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000569int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 SkDEBUGCODE(this->validate();)
571
572 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000573 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000574 int count = SkMin32(max, fPathRef->countPoints());
575 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
576 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577}
578
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000579SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000580 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
581 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000582 }
583 return SkPoint::Make(0, 0);
584}
585
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000586int SkPath::countVerbs() const {
587 return fPathRef->countVerbs();
588}
589
590static inline void copy_verbs_reverse(uint8_t* inorderDst,
591 const uint8_t* reversedSrc,
592 int count) {
593 for (int i = 0; i < count; ++i) {
594 inorderDst[i] = reversedSrc[~i];
595 }
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getVerbs(uint8_t dst[], int max) const {
599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countVerbs());
604 copy_verbs_reverse(dst, fPathRef->verbs(), count);
605 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606}
607
reed@google.com294dd7b2011-10-11 11:58:32 +0000608bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 SkDEBUGCODE(this->validate();)
610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000612 if (count > 0) {
613 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000616 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 if (lastPt) {
619 lastPt->set(0, 0);
620 }
621 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622}
623
624void SkPath::setLastPt(SkScalar x, SkScalar y) {
625 SkDEBUGCODE(this->validate();)
626
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 if (count == 0) {
629 this->moveTo(x, y);
630 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 SkPathRef::Editor ed(&fPathRef);
632 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 }
634}
635
reed@google.com04863fa2011-05-15 04:08:24 +0000636void SkPath::setConvexity(Convexity c) {
637 if (fConvexity != c) {
638 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000639 }
640}
641
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642//////////////////////////////////////////////////////////////////////////////
643// Construction methods
644
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000645#define DIRTY_AFTER_EDIT \
646 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000647 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000648 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000649 } while (0)
650
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651void SkPath::incReserve(U16CPU inc) {
652 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654 SkDEBUGCODE(this->validate();)
655}
656
657void SkPath::moveTo(SkScalar x, SkScalar y) {
658 SkDEBUGCODE(this->validate();)
659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000660 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661
reed@google.comd335d1d2012-01-12 18:17:11 +0000662 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000663 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000664
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666}
667
668void SkPath::rMoveTo(SkScalar x, SkScalar y) {
669 SkPoint pt;
670 this->getLastPt(&pt);
671 this->moveTo(pt.fX + x, pt.fY + y);
672}
673
reed@google.comd335d1d2012-01-12 18:17:11 +0000674void SkPath::injectMoveToIfNeeded() {
675 if (fLastMoveToIndex < 0) {
676 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000678 x = y = 0;
679 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000681 x = pt.fX;
682 y = pt.fY;
683 }
684 this->moveTo(x, y);
685 }
686}
687
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688void SkPath::lineTo(SkScalar x, SkScalar y) {
689 SkDEBUGCODE(this->validate();)
690
reed@google.comd335d1d2012-01-12 18:17:11 +0000691 this->injectMoveToIfNeeded();
692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 SkPathRef::Editor ed(&fPathRef);
694 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695
reed@google.comb54455e2011-05-16 14:16:04 +0000696 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697}
698
699void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000700 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701 SkPoint pt;
702 this->getLastPt(&pt);
703 this->lineTo(pt.fX + x, pt.fY + y);
704}
705
706void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
707 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000708
reed@google.comd335d1d2012-01-12 18:17:11 +0000709 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000710
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000711 SkPathRef::Editor ed(&fPathRef);
712 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 pts[0].set(x1, y1);
714 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000715
reed@google.comb54455e2011-05-16 14:16:04 +0000716 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717}
718
719void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000720 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721 SkPoint pt;
722 this->getLastPt(&pt);
723 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
724}
725
reed@google.com277c3f82013-05-31 15:17:50 +0000726void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
727 SkScalar w) {
728 // check for <= 0 or NaN with this test
729 if (!(w > 0)) {
730 this->lineTo(x2, y2);
731 } else if (!SkScalarIsFinite(w)) {
732 this->lineTo(x1, y1);
733 this->lineTo(x2, y2);
734 } else if (SK_Scalar1 == w) {
735 this->quadTo(x1, y1, x2, y2);
736 } else {
737 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000738
reed@google.com277c3f82013-05-31 15:17:50 +0000739 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000740
reed@google.com277c3f82013-05-31 15:17:50 +0000741 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000742 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000743 pts[0].set(x1, y1);
744 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000745
reed@google.com277c3f82013-05-31 15:17:50 +0000746 DIRTY_AFTER_EDIT;
747 }
748}
749
750void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
751 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000752 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000753 SkPoint pt;
754 this->getLastPt(&pt);
755 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
756}
757
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
759 SkScalar x3, SkScalar y3) {
760 SkDEBUGCODE(this->validate();)
761
reed@google.comd335d1d2012-01-12 18:17:11 +0000762 this->injectMoveToIfNeeded();
763
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000764 SkPathRef::Editor ed(&fPathRef);
765 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 pts[0].set(x1, y1);
767 pts[1].set(x2, y2);
768 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769
reed@google.comb54455e2011-05-16 14:16:04 +0000770 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771}
772
773void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
774 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000775 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 SkPoint pt;
777 this->getLastPt(&pt);
778 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
779 pt.fX + x3, pt.fY + y3);
780}
781
782void SkPath::close() {
783 SkDEBUGCODE(this->validate();)
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000787 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 case kLine_Verb:
789 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000790 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000792 case kMove_Verb: {
793 SkPathRef::Editor ed(&fPathRef);
794 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000796 }
reed@google.com277c3f82013-05-31 15:17:50 +0000797 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000798 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000799 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000800 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000801 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000802 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 }
804 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000805
806 // signal that we need a moveTo to follow us (unless we're done)
807#if 0
808 if (fLastMoveToIndex >= 0) {
809 fLastMoveToIndex = ~fLastMoveToIndex;
810 }
811#else
812 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
813#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814}
815
816///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000817
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000818static void assert_known_direction(int dir) {
819 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
820}
821
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822void SkPath::addRect(const SkRect& rect, Direction dir) {
823 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
824}
825
826void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
827 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000828 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000829 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
830 SkAutoDisableDirectionCheck addc(this);
831
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
833
834 this->incReserve(5);
835
836 this->moveTo(left, top);
837 if (dir == kCCW_Direction) {
838 this->lineTo(left, bottom);
839 this->lineTo(right, bottom);
840 this->lineTo(right, top);
841 } else {
842 this->lineTo(right, top);
843 this->lineTo(right, bottom);
844 this->lineTo(left, bottom);
845 }
846 this->close();
847}
848
reed@google.com744faba2012-05-29 19:54:52 +0000849void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
850 SkDEBUGCODE(this->validate();)
851 if (count <= 0) {
852 return;
853 }
854
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000855 fLastMoveToIndex = fPathRef->countPoints();
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000856
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000857 // +close makes room for the extra kClose_Verb
858 SkPathRef::Editor ed(&fPathRef, count+close, count);
859
860 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000861 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000862 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
863 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000864 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000865
reed@google.com744faba2012-05-29 19:54:52 +0000866 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000867 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000868 }
869
reed@google.com744faba2012-05-29 19:54:52 +0000870 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000871 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000872}
873
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000874#include "SkGeometry.h"
875
876static int build_arc_points(const SkRect& oval, SkScalar startAngle,
877 SkScalar sweepAngle,
878 SkPoint pts[kSkBuildQuadArcStorage]) {
879
880 if (0 == sweepAngle &&
881 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
882 // Chrome uses this path to move into and out of ovals. If not
883 // treated as a special case the moves can distort the oval's
884 // bounding box (and break the circle special case).
885 pts[0].set(oval.fRight, oval.centerY());
886 return 1;
887 } else if (0 == oval.width() && 0 == oval.height()) {
888 // Chrome will sometimes create 0 radius round rects. Having degenerate
889 // quad segments in the path prevents the path from being recognized as
890 // a rect.
891 // TODO: optimizing the case where only one of width or height is zero
892 // should also be considered. This case, however, doesn't seem to be
893 // as common as the single point case.
894 pts[0].set(oval.fRight, oval.fTop);
895 return 1;
896 }
897
898 SkVector start, stop;
899
900 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
901 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
902 &stop.fX);
903
904 /* If the sweep angle is nearly (but less than) 360, then due to precision
905 loss in radians-conversion and/or sin/cos, we may end up with coincident
906 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
907 of drawing a nearly complete circle (good).
908 e.g. canvas.drawArc(0, 359.99, ...)
909 -vs- canvas.drawArc(0, 359.9, ...)
910 We try to detect this edge case, and tweak the stop vector
911 */
912 if (start == stop) {
913 SkScalar sw = SkScalarAbs(sweepAngle);
914 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
915 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
916 // make a guess at a tiny angle (in radians) to tweak by
917 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
918 // not sure how much will be enough, so we use a loop
919 do {
920 stopRad -= deltaRad;
921 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
922 } while (start == stop);
923 }
924 }
925
926 SkMatrix matrix;
927
928 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
929 matrix.postTranslate(oval.centerX(), oval.centerY());
930
931 return SkBuildQuadArc(start, stop,
932 sweepAngle > 0 ? kCW_SkRotationDirection :
933 kCCW_SkRotationDirection,
934 &matrix, pts);
935}
936
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000937void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000939 SkRRect rrect;
940 rrect.setRectRadii(rect, (const SkVector*) radii);
941 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942}
943
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000944/* The inline clockwise and counterclockwise round rect quad approximations
945 make it easier to see the symmetry patterns used by add corner quads.
946Clockwise corner value
947 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
948 path->quadTo(rect.fLeft, rect.fTop + offPtY,
949 rect.fLeft + midPtX, rect.fTop + midPtY);
950 path->quadTo(rect.fLeft + offPtX, rect.fTop,
951 rect.fLeft + rx, rect.fTop);
952
953 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
954 path->quadTo(rect.fRight - offPtX, rect.fTop,
955 rect.fRight - midPtX, rect.fTop + midPtY);
956 path->quadTo(rect.fRight, rect.fTop + offPtY,
957 rect.fRight, rect.fTop + ry);
958
959 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
960 path->quadTo(rect.fRight, rect.fBottom - offPtY,
961 rect.fRight - midPtX, rect.fBottom - midPtY);
962 path->quadTo(rect.fRight - offPtX, rect.fBottom,
963 rect.fRight - rx, rect.fBottom);
964
965 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
966 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
967 rect.fLeft + midPtX, rect.fBottom - midPtY);
968 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
969 rect.fLeft, rect.fBottom - ry);
970
971Counterclockwise
972 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
973 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
974 rect.fLeft + midPtX, rect.fBottom - midPtY);
975 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
976 rect.fLeft + rx, rect.fBottom);
977
978 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
979 path->quadTo(rect.fRight - offPtX, rect.fBottom,
980 rect.fRight - midPtX, rect.fBottom - midPtY);
981 path->quadTo(rect.fRight, rect.fBottom - offPtY,
982 rect.fRight, rect.fBottom - ry);
983
984 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
985 path->quadTo(rect.fRight, rect.fTop + offPtY,
986 rect.fRight - midPtX, rect.fTop + midPtY);
987 path->quadTo(rect.fRight - offPtX, rect.fTop,
988 rect.fRight - rx, rect.fTop);
989
990 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
991 path->quadTo(rect.fLeft + offPtX, rect.fTop,
992 rect.fLeft + midPtX, rect.fTop + midPtY);
993 path->quadTo(rect.fLeft, rect.fTop + offPtY,
994 rect.fLeft, rect.fTop + ry);
995*/
996static void add_corner_quads(SkPath* path, const SkRRect& rrect,
997 SkRRect::Corner corner, SkPath::Direction dir) {
998 const SkRect& rect = rrect.rect();
999 const SkVector& radii = rrect.radii(corner);
1000 SkScalar rx = radii.fX;
1001 SkScalar ry = radii.fY;
1002 // The mid point of the quadratic arc approximation is half way between the two
1003 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001004 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1005 SkScalar midPtX = rx * mid;
1006 SkScalar midPtY = ry * mid;
1007 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1008 SkScalar offPtX = rx * control;
1009 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001010 static const int kCornerPts = 5;
1011 SkScalar xOff[kCornerPts];
1012 SkScalar yOff[kCornerPts];
1013
1014 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1015 SkASSERT(dir == SkPath::kCCW_Direction
1016 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1017 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1018 xOff[0] = xOff[1] = 0;
1019 xOff[2] = midPtX;
1020 xOff[3] = offPtX;
1021 xOff[4] = rx;
1022 yOff[0] = ry;
1023 yOff[1] = offPtY;
1024 yOff[2] = midPtY;
1025 yOff[3] = yOff[4] = 0;
1026 } else {
1027 xOff[0] = rx;
1028 xOff[1] = offPtX;
1029 xOff[2] = midPtX;
1030 xOff[3] = xOff[4] = 0;
1031 yOff[0] = yOff[1] = 0;
1032 yOff[2] = midPtY;
1033 yOff[3] = offPtY;
1034 yOff[4] = ry;
1035 }
1036 if ((corner - 1) & 2) {
1037 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1038 for (int i = 0; i < kCornerPts; ++i) {
1039 xOff[i] = rect.fLeft + xOff[i];
1040 }
1041 } else {
1042 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1043 for (int i = 0; i < kCornerPts; ++i) {
1044 xOff[i] = rect.fRight - xOff[i];
1045 }
1046 }
1047 if (corner < SkRRect::kLowerRight_Corner) {
1048 for (int i = 0; i < kCornerPts; ++i) {
1049 yOff[i] = rect.fTop + yOff[i];
1050 }
1051 } else {
1052 for (int i = 0; i < kCornerPts; ++i) {
1053 yOff[i] = rect.fBottom - yOff[i];
1054 }
1055 }
1056
1057 SkPoint lastPt;
1058 SkAssertResult(path->getLastPt(&lastPt));
1059 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1060 path->lineTo(xOff[0], yOff[0]);
1061 }
1062 if (rx || ry) {
1063 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1064 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1065 } else {
1066 path->lineTo(xOff[2], yOff[2]);
1067 path->lineTo(xOff[4], yOff[4]);
1068 }
1069}
1070
reed@google.com4ed0fb72012-12-12 20:48:18 +00001071void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001072 assert_known_direction(dir);
1073
1074 if (rrect.isEmpty()) {
1075 return;
1076 }
1077
reed@google.com4ed0fb72012-12-12 20:48:18 +00001078 const SkRect& bounds = rrect.getBounds();
1079
1080 if (rrect.isRect()) {
1081 this->addRect(bounds, dir);
1082 } else if (rrect.isOval()) {
1083 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001084#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001085 } else if (rrect.isSimple()) {
1086 const SkVector& rad = rrect.getSimpleRadii();
1087 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001088#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001089 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001090 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001091
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001092 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001093 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001094
1095 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001097 this->moveTo(bounds.fLeft,
1098 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1099 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1100 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1101 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1102 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001103 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001104 this->moveTo(bounds.fLeft,
1105 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1106 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1107 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1108 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1109 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110 }
1111 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001112 }
1113}
1114
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001115bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001116 int count = fPathRef->countVerbs();
1117 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1118 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001119 if (*verbs == kLine_Verb ||
1120 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001121 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001122 *verbs == kCubic_Verb) {
1123 return false;
1124 }
1125 ++verbs;
1126 }
1127 return true;
1128}
1129
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001130#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001131#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001132#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001133
1134void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1135 Direction dir) {
1136 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001137
humper@google.com75e3ca12013-04-08 21:44:11 +00001138 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001139 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001140 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001141 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001142 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1143 return;
1144 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001145
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001146#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147 SkScalar w = rect.width();
1148 SkScalar halfW = SkScalarHalf(w);
1149 SkScalar h = rect.height();
1150 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001151
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001152 if (halfW <= 0 || halfH <= 0) {
1153 return;
1154 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001155
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001156 bool skip_hori = rx >= halfW;
1157 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001158
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001159 if (skip_hori && skip_vert) {
1160 this->addOval(rect, dir);
1161 return;
1162 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001163
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001164 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001165
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001166 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001167 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001168
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001169 if (skip_hori) {
1170 rx = halfW;
1171 } else if (skip_vert) {
1172 ry = halfH;
1173 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001174 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1175 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001176
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001177 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001178 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001179 if (dir == kCCW_Direction) {
1180 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001181 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001182 }
1183 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1184 rect.fLeft, rect.fTop + ry - sy,
1185 rect.fLeft, rect.fTop + ry); // top-left
1186 if (!skip_vert) {
1187 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1188 }
1189 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1190 rect.fLeft + rx - sx, rect.fBottom,
1191 rect.fLeft + rx, rect.fBottom); // bot-left
1192 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001193 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001194 }
1195 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1196 rect.fRight, rect.fBottom - ry + sy,
1197 rect.fRight, rect.fBottom - ry); // bot-right
1198 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001199 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001200 }
1201 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1202 rect.fRight - rx + sx, rect.fTop,
1203 rect.fRight - rx, rect.fTop); // top-right
1204 } else {
1205 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1206 rect.fRight, rect.fTop + ry - sy,
1207 rect.fRight, rect.fTop + ry); // top-right
1208 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001209 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001210 }
1211 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1212 rect.fRight - rx + sx, rect.fBottom,
1213 rect.fRight - rx, rect.fBottom); // bot-right
1214 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001215 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001216 }
1217 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1218 rect.fLeft, rect.fBottom - ry + sy,
1219 rect.fLeft, rect.fBottom - ry); // bot-left
1220 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001221 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001222 }
1223 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1224 rect.fLeft + rx - sx, rect.fTop,
1225 rect.fLeft + rx, rect.fTop); // top-left
1226 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001227 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001228 }
1229 }
1230 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001231#else
1232 SkRRect rrect;
1233 rrect.setRectXY(rect, rx, ry);
1234 this->addRRect(rrect, dir);
1235#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001236}
1237
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001239 assert_known_direction(dir);
1240
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001241 /* If addOval() is called after previous moveTo(),
1242 this path is still marked as an oval. This is used to
1243 fit into WebKit's calling sequences.
1244 We can't simply check isEmpty() in this case, as additional
1245 moveTo() would mark the path non empty.
1246 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001247 bool isOval = hasOnlyMoveTos();
1248 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001249 fDirection = dir;
1250 } else {
1251 fDirection = kUnknown_Direction;
1252 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001255
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 SkAutoPathBoundsUpdate apbu(this, oval);
1257
1258 SkScalar cx = oval.centerX();
1259 SkScalar cy = oval.centerY();
1260 SkScalar rx = SkScalarHalf(oval.width());
1261 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1264 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1265 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1266 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1267
1268 /*
1269 To handle imprecision in computing the center and radii, we revert to
1270 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1271 to ensure that we don't exceed the oval's bounds *ever*, since we want
1272 to use oval for our fast-bounds, rather than have to recompute it.
1273 */
1274 const SkScalar L = oval.fLeft; // cx - rx
1275 const SkScalar T = oval.fTop; // cy - ry
1276 const SkScalar R = oval.fRight; // cx + rx
1277 const SkScalar B = oval.fBottom; // cy + ry
1278
1279 this->incReserve(17); // 8 quads + close
1280 this->moveTo(R, cy);
1281 if (dir == kCCW_Direction) {
1282 this->quadTo( R, cy - sy, cx + mx, cy - my);
1283 this->quadTo(cx + sx, T, cx , T);
1284 this->quadTo(cx - sx, T, cx - mx, cy - my);
1285 this->quadTo( L, cy - sy, L, cy );
1286 this->quadTo( L, cy + sy, cx - mx, cy + my);
1287 this->quadTo(cx - sx, B, cx , B);
1288 this->quadTo(cx + sx, B, cx + mx, cy + my);
1289 this->quadTo( R, cy + sy, R, cy );
1290 } else {
1291 this->quadTo( R, cy + sy, cx + mx, cy + my);
1292 this->quadTo(cx + sx, B, cx , B);
1293 this->quadTo(cx - sx, B, cx - mx, cy + my);
1294 this->quadTo( L, cy + sy, L, cy );
1295 this->quadTo( L, cy - sy, cx - mx, cy - my);
1296 this->quadTo(cx - sx, T, cx , T);
1297 this->quadTo(cx + sx, T, cx + mx, cy - my);
1298 this->quadTo( R, cy - sy, R, cy );
1299 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301
robertphillips@google.com466310d2013-12-03 16:43:54 +00001302 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001303
robertphillips@google.com466310d2013-12-03 16:43:54 +00001304 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001305}
1306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1308 if (r > 0) {
1309 SkRect rect;
1310 rect.set(x - r, y - r, x + r, y + r);
1311 this->addOval(rect, dir);
1312 }
1313}
1314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1316 bool forceMoveTo) {
1317 if (oval.width() < 0 || oval.height() < 0) {
1318 return;
1319 }
1320
1321 SkPoint pts[kSkBuildQuadArcStorage];
1322 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1323 SkASSERT((count & 1) == 1);
1324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001325 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 forceMoveTo = true;
1327 }
1328 this->incReserve(count);
1329 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1330 for (int i = 1; i < count; i += 2) {
1331 this->quadTo(pts[i], pts[i+1]);
1332 }
1333}
1334
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001335void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 if (oval.isEmpty() || 0 == sweepAngle) {
1337 return;
1338 }
1339
1340 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1341
1342 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1343 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1344 return;
1345 }
1346
1347 SkPoint pts[kSkBuildQuadArcStorage];
1348 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1349
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001350 SkDEBUGCODE(this->validate();)
1351 SkASSERT(count & 1);
1352
1353 fLastMoveToIndex = fPathRef->countPoints();
1354
1355 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1356
1357 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1358 if (count > 1) {
1359 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1360 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001362
1363 DIRTY_AFTER_EDIT;
1364 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365}
1366
1367/*
1368 Need to handle the case when the angle is sharp, and our computed end-points
1369 for the arc go behind pt1 and/or p2...
1370*/
1371void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1372 SkScalar radius) {
1373 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001374
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375 // need to know our prev pt so we can construct tangent vectors
1376 {
1377 SkPoint start;
1378 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001379 // Handle degenerate cases by adding a line to the first point and
1380 // bailing out.
1381 if ((x1 == start.fX && y1 == start.fY) ||
1382 (x1 == x2 && y1 == y2) ||
1383 radius == 0) {
1384 this->lineTo(x1, y1);
1385 return;
1386 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387 before.setNormalize(x1 - start.fX, y1 - start.fY);
1388 after.setNormalize(x2 - x1, y2 - y1);
1389 }
reed@google.comabf15c12011-01-18 20:35:51 +00001390
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 SkScalar cosh = SkPoint::DotProduct(before, after);
1392 SkScalar sinh = SkPoint::CrossProduct(before, after);
1393
1394 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001395 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 return;
1397 }
reed@google.comabf15c12011-01-18 20:35:51 +00001398
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1400 if (dist < 0) {
1401 dist = -dist;
1402 }
1403
1404 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1405 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1406 SkRotationDirection arcDir;
1407
1408 // now turn before/after into normals
1409 if (sinh > 0) {
1410 before.rotateCCW();
1411 after.rotateCCW();
1412 arcDir = kCW_SkRotationDirection;
1413 } else {
1414 before.rotateCW();
1415 after.rotateCW();
1416 arcDir = kCCW_SkRotationDirection;
1417 }
1418
1419 SkMatrix matrix;
1420 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001421
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 matrix.setScale(radius, radius);
1423 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1424 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001425
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001427
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 this->incReserve(count);
1429 // [xx,yy] == pts[0]
1430 this->lineTo(xx, yy);
1431 for (int i = 1; i < count; i += 2) {
1432 this->quadTo(pts[i], pts[i+1]);
1433 }
1434}
1435
1436///////////////////////////////////////////////////////////////////////////////
1437
1438void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1439 SkMatrix matrix;
1440
1441 matrix.setTranslate(dx, dy);
1442 this->addPath(path, matrix);
1443}
1444
1445void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001446 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001448 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 SkPoint pts[4];
1450 Verb verb;
1451
1452 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1453
1454 while ((verb = iter.next(pts)) != kDone_Verb) {
1455 switch (verb) {
1456 case kMove_Verb:
1457 proc(matrix, &pts[0], &pts[0], 1);
1458 this->moveTo(pts[0]);
1459 break;
1460 case kLine_Verb:
1461 proc(matrix, &pts[1], &pts[1], 1);
1462 this->lineTo(pts[1]);
1463 break;
1464 case kQuad_Verb:
1465 proc(matrix, &pts[1], &pts[1], 2);
1466 this->quadTo(pts[1], pts[2]);
1467 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001468 case kConic_Verb:
1469 proc(matrix, &pts[1], &pts[1], 2);
1470 this->conicTo(pts[1], pts[2], iter.conicWeight());
1471 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472 case kCubic_Verb:
1473 proc(matrix, &pts[1], &pts[1], 3);
1474 this->cubicTo(pts[1], pts[2], pts[3]);
1475 break;
1476 case kClose_Verb:
1477 this->close();
1478 break;
1479 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001480 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 }
1482 }
1483}
1484
1485///////////////////////////////////////////////////////////////////////////////
1486
reed@google.com277c3f82013-05-31 15:17:50 +00001487static int pts_in_verb(unsigned verb) {
1488 static const uint8_t gPtsInVerb[] = {
1489 1, // kMove
1490 1, // kLine
1491 2, // kQuad
1492 2, // kConic
1493 3, // kCubic
1494 0, // kClose
1495 0 // kDone
1496 };
1497
1498 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1499 return gPtsInVerb[verb];
1500}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502// ignore the last point of the 1st contour
1503void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001504 int i, vcount = path.fPathRef->countVerbs();
1505 // exit early if the path is empty, or just has a moveTo.
1506 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 return;
1508 }
1509
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001510 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001512 const uint8_t* verbs = path.fPathRef->verbs();
1513 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001514 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001516 SkASSERT(verbs[~0] == kMove_Verb);
1517 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001518 unsigned v = verbs[~i];
1519 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 if (n == 0) {
1521 break;
1522 }
1523 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001524 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 }
1526
1527 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 case kLine_Verb:
1530 this->lineTo(pts[-1].fX, pts[-1].fY);
1531 break;
1532 case kQuad_Verb:
1533 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1534 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001535 case kConic_Verb:
1536 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1537 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 case kCubic_Verb:
1539 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1540 pts[-3].fX, pts[-3].fY);
1541 break;
1542 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001543 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 break;
1545 }
reed@google.com277c3f82013-05-31 15:17:50 +00001546 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 }
1548}
1549
reed@google.com63d73742012-01-10 15:33:12 +00001550void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001551 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001552
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 const SkPoint* pts = src.fPathRef->pointsEnd();
1554 // we will iterator through src's verbs backwards
1555 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1556 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001557 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001558
1559 bool needMove = true;
1560 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001561 while (verbs < verbsEnd) {
1562 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001563 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001564
1565 if (needMove) {
1566 --pts;
1567 this->moveTo(pts->fX, pts->fY);
1568 needMove = false;
1569 }
1570 pts -= n;
1571 switch (v) {
1572 case kMove_Verb:
1573 if (needClose) {
1574 this->close();
1575 needClose = false;
1576 }
1577 needMove = true;
1578 pts += 1; // so we see the point in "if (needMove)" above
1579 break;
1580 case kLine_Verb:
1581 this->lineTo(pts[0]);
1582 break;
1583 case kQuad_Verb:
1584 this->quadTo(pts[1], pts[0]);
1585 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001586 case kConic_Verb:
1587 this->conicTo(pts[1], pts[0], *--conicWeights);
1588 break;
reed@google.com63d73742012-01-10 15:33:12 +00001589 case kCubic_Verb:
1590 this->cubicTo(pts[2], pts[1], pts[0]);
1591 break;
1592 case kClose_Verb:
1593 needClose = true;
1594 break;
1595 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001596 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001597 }
1598 }
1599}
1600
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601///////////////////////////////////////////////////////////////////////////////
1602
1603void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1604 SkMatrix matrix;
1605
1606 matrix.setTranslate(dx, dy);
1607 this->transform(matrix, dst);
1608}
1609
1610#include "SkGeometry.h"
1611
1612static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1613 int level = 2) {
1614 if (--level >= 0) {
1615 SkPoint tmp[5];
1616
1617 SkChopQuadAtHalf(pts, tmp);
1618 subdivide_quad_to(path, &tmp[0], level);
1619 subdivide_quad_to(path, &tmp[2], level);
1620 } else {
1621 path->quadTo(pts[1], pts[2]);
1622 }
1623}
1624
1625static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1626 int level = 2) {
1627 if (--level >= 0) {
1628 SkPoint tmp[7];
1629
1630 SkChopCubicAtHalf(pts, tmp);
1631 subdivide_cubic_to(path, &tmp[0], level);
1632 subdivide_cubic_to(path, &tmp[3], level);
1633 } else {
1634 path->cubicTo(pts[1], pts[2], pts[3]);
1635 }
1636}
1637
1638void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1639 SkDEBUGCODE(this->validate();)
1640 if (dst == NULL) {
1641 dst = (SkPath*)this;
1642 }
1643
tomhudson@google.com8d430182011-06-06 19:11:19 +00001644 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 SkPath tmp;
1646 tmp.fFillType = fFillType;
1647
1648 SkPath::Iter iter(*this, false);
1649 SkPoint pts[4];
1650 SkPath::Verb verb;
1651
reed@google.com4a3b7142012-05-16 17:16:46 +00001652 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 switch (verb) {
1654 case kMove_Verb:
1655 tmp.moveTo(pts[0]);
1656 break;
1657 case kLine_Verb:
1658 tmp.lineTo(pts[1]);
1659 break;
1660 case kQuad_Verb:
1661 subdivide_quad_to(&tmp, pts);
1662 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001663 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001664 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001665 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1666 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 case kCubic_Verb:
1668 subdivide_cubic_to(&tmp, pts);
1669 break;
1670 case kClose_Verb:
1671 tmp.close();
1672 break;
1673 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001674 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 break;
1676 }
1677 }
1678
1679 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001680 SkPathRef::Editor ed(&dst->fPathRef);
1681 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001682 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001684 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001688 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001690
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001691 if (kUnknown_Direction == fDirection) {
1692 dst->fDirection = kUnknown_Direction;
1693 } else {
1694 SkScalar det2x2 =
1695 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1696 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1697 if (det2x2 < 0) {
1698 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1699 } else if (det2x2 > 0) {
1700 dst->fDirection = fDirection;
1701 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001702 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001703 dst->fDirection = kUnknown_Direction;
1704 }
1705 }
1706
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 SkDEBUGCODE(dst->validate();)
1708 }
1709}
1710
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711///////////////////////////////////////////////////////////////////////////////
1712///////////////////////////////////////////////////////////////////////////////
1713
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001714enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001715 kEmptyContour_SegmentState, // The current contour is empty. We may be
1716 // starting processing or we may have just
1717 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001718 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1719 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1720 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721};
1722
1723SkPath::Iter::Iter() {
1724#ifdef SK_DEBUG
1725 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001726 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730#endif
1731 // need to init enough to make next() harmlessly return kDone_Verb
1732 fVerbs = NULL;
1733 fVerbStop = NULL;
1734 fNeedClose = false;
1735}
1736
1737SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1738 this->setPath(path, forceClose);
1739}
1740
1741void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 fPts = path.fPathRef->points();
1743 fVerbs = path.fPathRef->verbs();
1744 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001745 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001746 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001747 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 fForceClose = SkToU8(forceClose);
1749 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001750 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751}
1752
1753bool SkPath::Iter::isClosedContour() const {
1754 if (fVerbs == NULL || fVerbs == fVerbStop) {
1755 return false;
1756 }
1757 if (fForceClose) {
1758 return true;
1759 }
1760
1761 const uint8_t* verbs = fVerbs;
1762 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001763
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 if (kMove_Verb == *(verbs - 1)) {
1765 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 }
1767
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001768 while (verbs > stop) {
1769 // verbs points one beyond the current verb, decrement first.
1770 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 if (kMove_Verb == v) {
1772 break;
1773 }
1774 if (kClose_Verb == v) {
1775 return true;
1776 }
1777 }
1778 return false;
1779}
1780
1781SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001782 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001784 // A special case: if both points are NaN, SkPoint::operation== returns
1785 // false, but the iterator expects that they are treated as the same.
1786 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001787 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1788 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001789 return kClose_Verb;
1790 }
1791
reed@google.com9e25dbf2012-05-15 17:05:38 +00001792 pts[0] = fLastPt;
1793 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 fLastPt = fMoveTo;
1795 fCloseLine = true;
1796 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001797 } else {
1798 pts[0] = fMoveTo;
1799 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801}
1802
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 if (fSegmentState == kAfterMove_SegmentState) {
1805 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001807 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1810 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001811 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813}
1814
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815void SkPath::Iter::consumeDegenerateSegments() {
1816 // We need to step over anything that will not move the current draw point
1817 // forward before the next move is seen
1818 const uint8_t* lastMoveVerb = 0;
1819 const SkPoint* lastMovePt = 0;
1820 SkPoint lastPt = fLastPt;
1821 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001822 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 switch (verb) {
1824 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 // Keep a record of this most recent move
1826 lastMoveVerb = fVerbs;
1827 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001828 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001829 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 fPts++;
1831 break;
1832
1833 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001834 // A close when we are in a segment is always valid except when it
1835 // follows a move which follows a segment.
1836 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 return;
1838 }
1839 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001840 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001841 break;
1842
1843 case kLine_Verb:
1844 if (!IsLineDegenerate(lastPt, fPts[0])) {
1845 if (lastMoveVerb) {
1846 fVerbs = lastMoveVerb;
1847 fPts = lastMovePt;
1848 return;
1849 }
1850 return;
1851 }
1852 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001853 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 fPts++;
1855 break;
1856
reed@google.com277c3f82013-05-31 15:17:50 +00001857 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 case kQuad_Verb:
1859 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1860 if (lastMoveVerb) {
1861 fVerbs = lastMoveVerb;
1862 fPts = lastMovePt;
1863 return;
1864 }
1865 return;
1866 }
1867 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001869 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001870 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 break;
1872
1873 case kCubic_Verb:
1874 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1875 if (lastMoveVerb) {
1876 fVerbs = lastMoveVerb;
1877 fPts = lastMovePt;
1878 return;
1879 }
1880 return;
1881 }
1882 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 fPts += 3;
1885 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001886
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001888 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 }
1890 }
1891}
1892
reed@google.com4a3b7142012-05-16 17:16:46 +00001893SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001894 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 // Close the curve if requested and if there is some curve to close
1898 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 return kLine_Verb;
1901 }
1902 fNeedClose = false;
1903 return kClose_Verb;
1904 }
1905 return kDone_Verb;
1906 }
1907
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001908 // fVerbs is one beyond the current verb, decrement first
1909 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 const SkPoint* SK_RESTRICT srcPts = fPts;
1911 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912
1913 switch (verb) {
1914 case kMove_Verb:
1915 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 verb = this->autoClose(pts);
1918 if (verb == kClose_Verb) {
1919 fNeedClose = false;
1920 }
1921 return (Verb)verb;
1922 }
1923 if (fVerbs == fVerbStop) { // might be a trailing moveto
1924 return kDone_Verb;
1925 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001926 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001927 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001928 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001929 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 fNeedClose = fForceClose;
1932 break;
1933 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001934 pts[0] = this->cons_moveTo();
1935 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 fLastPt = srcPts[0];
1937 fCloseLine = false;
1938 srcPts += 1;
1939 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001940 case kConic_Verb:
1941 fConicWeights += 1;
1942 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001944 pts[0] = this->cons_moveTo();
1945 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 fLastPt = srcPts[1];
1947 srcPts += 2;
1948 break;
1949 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001950 pts[0] = this->cons_moveTo();
1951 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 fLastPt = srcPts[2];
1953 srcPts += 3;
1954 break;
1955 case kClose_Verb:
1956 verb = this->autoClose(pts);
1957 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001958 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 } else {
1960 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001961 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 break;
1965 }
1966 fPts = srcPts;
1967 return (Verb)verb;
1968}
1969
1970///////////////////////////////////////////////////////////////////////////////
1971
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001972SkPath::RawIter::RawIter() {
1973#ifdef SK_DEBUG
1974 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001975 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001976 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1977#endif
1978 // need to init enough to make next() harmlessly return kDone_Verb
1979 fVerbs = NULL;
1980 fVerbStop = NULL;
1981}
1982
1983SkPath::RawIter::RawIter(const SkPath& path) {
1984 this->setPath(path);
1985}
1986
1987void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001988 fPts = path.fPathRef->points();
1989 fVerbs = path.fPathRef->verbs();
1990 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001991 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992 fMoveTo.fX = fMoveTo.fY = 0;
1993 fLastPt.fX = fLastPt.fY = 0;
1994}
1995
1996SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001997 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 if (fVerbs == fVerbStop) {
1999 return kDone_Verb;
2000 }
2001
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002002 // fVerbs points one beyond next verb so decrement first.
2003 unsigned verb = *(--fVerbs);
2004 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005
2006 switch (verb) {
2007 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002008 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002009 fMoveTo = srcPts[0];
2010 fLastPt = fMoveTo;
2011 srcPts += 1;
2012 break;
2013 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002014 pts[0] = fLastPt;
2015 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002016 fLastPt = srcPts[0];
2017 srcPts += 1;
2018 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002019 case kConic_Verb:
2020 fConicWeights += 1;
2021 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002022 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002023 pts[0] = fLastPt;
2024 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002025 fLastPt = srcPts[1];
2026 srcPts += 2;
2027 break;
2028 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002029 pts[0] = fLastPt;
2030 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002031 fLastPt = srcPts[2];
2032 srcPts += 3;
2033 break;
2034 case kClose_Verb:
2035 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002036 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 break;
2038 }
2039 fPts = srcPts;
2040 return (Verb)verb;
2041}
2042
2043///////////////////////////////////////////////////////////////////////////////
2044
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002046 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047*/
2048
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002049size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 SkDEBUGCODE(this->validate();)
2051
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002052 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002053 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002054 return SkAlign4(byteCount);
2055 }
2056
2057 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
robertphillips@google.com466310d2013-12-03 16:43:54 +00002059 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002060 (fFillType << kFillType_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002061 (fDirection << kDirection_SerializationShift)
robertphillips@google.com466310d2013-12-03 16:43:54 +00002062#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
robertphillips@google.com11e05552013-12-03 19:46:58 +00002063 | (0x1 << kNewFormat_SerializationShift)
robertphillips@google.com466310d2013-12-03 16:43:54 +00002064#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002065 ;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002066
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002067 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002068
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002069 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002070
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002071 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002072 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073}
2074
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002075size_t SkPath::readFromMemory(const void* storage, size_t length) {
2076 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002078 int32_t packed;
2079 if (!buffer.readS32(&packed)) {
2080 return 0;
2081 }
2082
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2084 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002085 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com466310d2013-12-03 16:43:54 +00002086#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
robertphillips@google.com11e05552013-12-03 19:46:58 +00002087 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002088#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002090 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer
robertphillips@google.com466310d2013-12-03 16:43:54 +00002091#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002092 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002093#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002094 );
reed@google.comabf15c12011-01-18 20:35:51 +00002095
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002096 size_t sizeRead = 0;
2097 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002098 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002099 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002100 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002101 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002102 } else if (NULL != pathRef) {
2103 // If the buffer is not valid, pathRef should be NULL
2104 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002105 }
2106 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002107}
2108
2109///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110
reed@google.com51bbe752013-01-17 22:07:50 +00002111#include "SkString.h"
2112
2113static void append_scalar(SkString* str, SkScalar value) {
2114 SkString tmp;
2115 tmp.printf("%g", value);
2116 if (tmp.contains('.')) {
2117 tmp.appendUnichar('f');
2118 }
2119 str->append(tmp);
2120}
2121
2122static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002123 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002124 str->append(label);
2125 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002126
reed@google.com51bbe752013-01-17 22:07:50 +00002127 const SkScalar* values = &pts[0].fX;
2128 count *= 2;
2129
2130 for (int i = 0; i < count; ++i) {
2131 append_scalar(str, values[i]);
2132 if (i < count - 1) {
2133 str->append(", ");
2134 }
2135 }
reed@google.com277c3f82013-05-31 15:17:50 +00002136 if (conicWeight >= 0) {
2137 str->append(", ");
2138 append_scalar(str, conicWeight);
2139 }
reed@google.com51bbe752013-01-17 22:07:50 +00002140 str->append(");\n");
2141}
2142
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143void SkPath::dump(bool forceClose, const char title[]) const {
2144 Iter iter(*this, forceClose);
2145 SkPoint pts[4];
2146 Verb verb;
2147
2148 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2149 title ? title : "");
2150
reed@google.com51bbe752013-01-17 22:07:50 +00002151 SkString builder;
2152
reed@google.com4a3b7142012-05-16 17:16:46 +00002153 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 switch (verb) {
2155 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002156 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 break;
2158 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002159 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 break;
2161 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002162 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002164 case kConic_Verb:
2165 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2166 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002167 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002168 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 break;
2170 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002171 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 break;
2173 default:
2174 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2175 verb = kDone_Verb; // stop the loop
2176 break;
2177 }
2178 }
reed@google.com51bbe752013-01-17 22:07:50 +00002179 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180}
2181
reed@android.come522ca52009-11-23 20:10:41 +00002182void SkPath::dump() const {
2183 this->dump(false);
2184}
2185
2186#ifdef SK_DEBUG
2187void SkPath::validate() const {
2188 SkASSERT(this != NULL);
2189 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002190
djsollen@google.com077348c2012-10-22 20:23:32 +00002191#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002192 if (!fBoundsIsDirty) {
2193 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002194
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002195 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002196 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002197
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002198 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002199 // if we're empty, fBounds may be empty but translated, so we can't
2200 // necessarily compare to bounds directly
2201 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2202 // be [2, 2, 2, 2]
2203 SkASSERT(bounds.isEmpty());
2204 SkASSERT(fBounds.isEmpty());
2205 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002206 if (bounds.isEmpty()) {
2207 SkASSERT(fBounds.isEmpty());
2208 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002209 if (!fBounds.isEmpty()) {
2210 SkASSERT(fBounds.contains(bounds));
2211 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002212 }
reed@android.come522ca52009-11-23 20:10:41 +00002213 }
2214 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002215#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002216}
djsollen@google.com077348c2012-10-22 20:23:32 +00002217#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002218
reed@google.com04863fa2011-05-15 04:08:24 +00002219///////////////////////////////////////////////////////////////////////////////
2220
reed@google.com0b7b9822011-05-16 12:29:27 +00002221static int sign(SkScalar x) { return x < 0; }
2222#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002223
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002224static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2225 // The error epsilon was empirically derived; worse case round rects
2226 // with a mid point outset by 2x float epsilon in tests had an error
2227 // of 12.
2228 const int epsilon = 16;
2229 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2230 return false;
2231 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002232 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002233 int aBits = SkFloatAs2sCompliment(compA);
2234 int bBits = SkFloatAs2sCompliment(compB);
2235 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002236}
2237
2238// only valid for a single contour
2239struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002240 Convexicator()
2241 : fPtCount(0)
2242 , fConvexity(SkPath::kConvex_Convexity)
2243 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002244 fSign = 0;
2245 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002246 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002247 fCurrPt.set(0, 0);
2248 fVec0.set(0, 0);
2249 fVec1.set(0, 0);
2250 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002251
2252 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002253 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002254 }
2255
2256 SkPath::Convexity getConvexity() const { return fConvexity; }
2257
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002258 /** The direction returned is only valid if the path is determined convex */
2259 SkPath::Direction getDirection() const { return fDirection; }
2260
reed@google.com04863fa2011-05-15 04:08:24 +00002261 void addPt(const SkPoint& pt) {
2262 if (SkPath::kConcave_Convexity == fConvexity) {
2263 return;
2264 }
2265
2266 if (0 == fPtCount) {
2267 fCurrPt = pt;
2268 ++fPtCount;
2269 } else {
2270 SkVector vec = pt - fCurrPt;
2271 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002272 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002273 fCurrPt = pt;
2274 if (++fPtCount == 2) {
2275 fFirstVec = fVec1 = vec;
2276 } else {
2277 SkASSERT(fPtCount > 2);
2278 this->addVec(vec);
2279 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002280
reed@google.com85b6e392011-05-15 20:25:17 +00002281 int sx = sign(vec.fX);
2282 int sy = sign(vec.fY);
2283 fDx += (sx != fSx);
2284 fDy += (sy != fSy);
2285 fSx = sx;
2286 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002287
reed@google.com85b6e392011-05-15 20:25:17 +00002288 if (fDx > 3 || fDy > 3) {
2289 fConvexity = SkPath::kConcave_Convexity;
2290 }
reed@google.com04863fa2011-05-15 04:08:24 +00002291 }
2292 }
2293 }
2294
2295 void close() {
2296 if (fPtCount > 2) {
2297 this->addVec(fFirstVec);
2298 }
2299 }
2300
2301private:
2302 void addVec(const SkVector& vec) {
2303 SkASSERT(vec.fX || vec.fY);
2304 fVec0 = fVec1;
2305 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002306 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2307 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2308 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2309 largest = SkTMax(largest, -smallest);
2310 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002311 if (0 == fSign) {
2312 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002313 if (1 == sign) {
2314 fDirection = SkPath::kCW_Direction;
2315 } else if (-1 == sign) {
2316 fDirection = SkPath::kCCW_Direction;
2317 }
reed@google.com04863fa2011-05-15 04:08:24 +00002318 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002319 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002320 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002321 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002322 }
2323 }
2324 }
2325
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002326 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002327 SkPoint fCurrPt;
2328 SkVector fVec0, fVec1, fFirstVec;
2329 int fPtCount; // non-degenerate points
2330 int fSign;
2331 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002332 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002333 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002334};
2335
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002336SkPath::Convexity SkPath::internalGetConvexity() const {
2337 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002338 SkPoint pts[4];
2339 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002340 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002341
2342 int contourCount = 0;
2343 int count;
2344 Convexicator state;
2345
2346 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2347 switch (verb) {
2348 case kMove_Verb:
2349 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002350 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002351 return kConcave_Convexity;
2352 }
2353 pts[1] = pts[0];
2354 count = 1;
2355 break;
2356 case kLine_Verb: count = 1; break;
2357 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002358 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002359 case kCubic_Verb: count = 3; break;
2360 case kClose_Verb:
2361 state.close();
2362 count = 0;
2363 break;
2364 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002365 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002366 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002367 return kConcave_Convexity;
2368 }
2369
2370 for (int i = 1; i <= count; i++) {
2371 state.addPt(pts[i]);
2372 }
2373 // early exit
2374 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002375 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002376 return kConcave_Convexity;
2377 }
2378 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002379 fConvexity = state.getConvexity();
2380 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2381 fDirection = state.getDirection();
2382 }
2383 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002384}
reed@google.com69a99432012-01-10 18:00:10 +00002385
2386///////////////////////////////////////////////////////////////////////////////
2387
2388class ContourIter {
2389public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002390 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002391
2392 bool done() const { return fDone; }
2393 // if !done() then these may be called
2394 int count() const { return fCurrPtCount; }
2395 const SkPoint* pts() const { return fCurrPt; }
2396 void next();
2397
2398private:
2399 int fCurrPtCount;
2400 const SkPoint* fCurrPt;
2401 const uint8_t* fCurrVerb;
2402 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002403 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002404 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002405 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002406};
2407
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002408ContourIter::ContourIter(const SkPathRef& pathRef) {
2409 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002410 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002411 fCurrPt = pathRef.points();
2412 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002413 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002414 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002415 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002416 this->next();
2417}
2418
2419void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002420 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002421 fDone = true;
2422 }
2423 if (fDone) {
2424 return;
2425 }
2426
2427 // skip pts of prev contour
2428 fCurrPt += fCurrPtCount;
2429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002431 int ptCount = 1; // moveTo
2432 const uint8_t* verbs = fCurrVerb;
2433
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002434 for (--verbs; verbs > fStopVerbs; --verbs) {
2435 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002436 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002437 goto CONTOUR_END;
2438 case SkPath::kLine_Verb:
2439 ptCount += 1;
2440 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002441 case SkPath::kConic_Verb:
2442 fCurrConicWeight += 1;
2443 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002444 case SkPath::kQuad_Verb:
2445 ptCount += 2;
2446 break;
2447 case SkPath::kCubic_Verb:
2448 ptCount += 3;
2449 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002450 case SkPath::kClose_Verb:
2451 break;
2452 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002453 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002454 break;
2455 }
2456 }
2457CONTOUR_END:
2458 fCurrPtCount = ptCount;
2459 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002460 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002461}
2462
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002463// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002464static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002465 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2466 // We may get 0 when the above subtracts underflow. We expect this to be
2467 // very rare and lazily promote to double.
2468 if (0 == cross) {
2469 double p0x = SkScalarToDouble(p0.fX);
2470 double p0y = SkScalarToDouble(p0.fY);
2471
2472 double p1x = SkScalarToDouble(p1.fX);
2473 double p1y = SkScalarToDouble(p1.fY);
2474
2475 double p2x = SkScalarToDouble(p2.fX);
2476 double p2y = SkScalarToDouble(p2.fY);
2477
2478 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2479 (p1y - p0y) * (p2x - p0x));
2480
2481 }
2482 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002483}
2484
reed@google.comc1ea60a2012-01-31 15:15:36 +00002485// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002486static int find_max_y(const SkPoint pts[], int count) {
2487 SkASSERT(count > 0);
2488 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002489 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002490 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002491 SkScalar y = pts[i].fY;
2492 if (y > max) {
2493 max = y;
2494 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002495 }
2496 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002497 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002498}
2499
reed@google.comcabaf1d2012-01-11 21:03:05 +00002500static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2501 int i = index;
2502 for (;;) {
2503 i = (i + inc) % n;
2504 if (i == index) { // we wrapped around, so abort
2505 break;
2506 }
2507 if (pts[index] != pts[i]) { // found a different point, success!
2508 break;
2509 }
2510 }
2511 return i;
2512}
2513
reed@google.comc1ea60a2012-01-31 15:15:36 +00002514/**
2515 * Starting at index, and moving forward (incrementing), find the xmin and
2516 * xmax of the contiguous points that have the same Y.
2517 */
2518static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2519 int* maxIndexPtr) {
2520 const SkScalar y = pts[index].fY;
2521 SkScalar min = pts[index].fX;
2522 SkScalar max = min;
2523 int minIndex = index;
2524 int maxIndex = index;
2525 for (int i = index + 1; i < n; ++i) {
2526 if (pts[i].fY != y) {
2527 break;
2528 }
2529 SkScalar x = pts[i].fX;
2530 if (x < min) {
2531 min = x;
2532 minIndex = i;
2533 } else if (x > max) {
2534 max = x;
2535 maxIndex = i;
2536 }
2537 }
2538 *maxIndexPtr = maxIndex;
2539 return minIndex;
2540}
2541
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002542static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002543 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002544}
2545
reed@google.comac8543f2012-01-30 20:51:25 +00002546/*
2547 * We loop through all contours, and keep the computed cross-product of the
2548 * contour that contained the global y-max. If we just look at the first
2549 * contour, we may find one that is wound the opposite way (correctly) since
2550 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2551 * that is outer most (or at least has the global y-max) before we can consider
2552 * its cross product.
2553 */
reed@google.com69a99432012-01-10 18:00:10 +00002554bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002555 if (kUnknown_Direction != fDirection) {
2556 *dir = static_cast<Direction>(fDirection);
2557 return true;
2558 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002559
2560 // don't want to pay the cost for computing this if it
2561 // is unknown, so we don't call isConvex()
2562 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2563 SkASSERT(kUnknown_Direction == fDirection);
2564 *dir = static_cast<Direction>(fDirection);
2565 return false;
2566 }
reed@google.com69a99432012-01-10 18:00:10 +00002567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002568 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002569
reed@google.comac8543f2012-01-30 20:51:25 +00002570 // initialize with our logical y-min
2571 SkScalar ymax = this->getBounds().fTop;
2572 SkScalar ymaxCross = 0;
2573
reed@google.com69a99432012-01-10 18:00:10 +00002574 for (; !iter.done(); iter.next()) {
2575 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002576 if (n < 3) {
2577 continue;
2578 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002579
reed@google.comcabaf1d2012-01-11 21:03:05 +00002580 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002581 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002582 int index = find_max_y(pts, n);
2583 if (pts[index].fY < ymax) {
2584 continue;
2585 }
2586
2587 // If there is more than 1 distinct point at the y-max, we take the
2588 // x-min and x-max of them and just subtract to compute the dir.
2589 if (pts[(index + 1) % n].fY == pts[index].fY) {
2590 int maxIndex;
2591 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2592 if (minIndex == maxIndex) {
2593 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002594 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002595 SkASSERT(pts[minIndex].fY == pts[index].fY);
2596 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2597 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2598 // we just subtract the indices, and let that auto-convert to
2599 // SkScalar, since we just want - or + to signal the direction.
2600 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002601 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002602 TRY_CROSSPROD:
2603 // Find a next and prev index to use for the cross-product test,
2604 // but we try to find pts that form non-zero vectors from pts[index]
2605 //
2606 // Its possible that we can't find two non-degenerate vectors, so
2607 // we have to guard our search (e.g. all the pts could be in the
2608 // same place).
2609
2610 // we pass n - 1 instead of -1 so we don't foul up % operator by
2611 // passing it a negative LH argument.
2612 int prev = find_diff_pt(pts, index, n, n - 1);
2613 if (prev == index) {
2614 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002615 continue;
2616 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002617 int next = find_diff_pt(pts, index, n, 1);
2618 SkASSERT(next != index);
2619 cross = cross_prod(pts[prev], pts[index], pts[next]);
2620 // if we get a zero and the points are horizontal, then we look at the spread in
2621 // x-direction. We really should continue to walk away from the degeneracy until
2622 // there is a divergence.
2623 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2624 // construct the subtract so we get the correct Direction below
2625 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002626 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002627 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002628
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002629 if (cross) {
2630 // record our best guess so far
2631 ymax = pts[index].fY;
2632 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002633 }
2634 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002635 if (ymaxCross) {
2636 crossToDir(ymaxCross, dir);
2637 fDirection = *dir;
2638 return true;
2639 } else {
2640 return false;
2641 }
reed@google.comac8543f2012-01-30 20:51:25 +00002642}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002643
2644///////////////////////////////////////////////////////////////////////////////
2645
2646static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2647 SkScalar D, SkScalar t) {
2648 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2649}
2650
2651static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2652 SkScalar t) {
2653 SkScalar A = c3 + 3*(c1 - c2) - c0;
2654 SkScalar B = 3*(c2 - c1 - c1 + c0);
2655 SkScalar C = 3*(c1 - c0);
2656 SkScalar D = c0;
2657 return eval_cubic_coeff(A, B, C, D, t);
2658}
2659
2660/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2661 t value such that cubic(t) = target
2662 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002663static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002664 SkScalar target, SkScalar* t) {
2665 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2666 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002667
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002668 SkScalar D = c0 - target;
2669 SkScalar A = c3 + 3*(c1 - c2) - c0;
2670 SkScalar B = 3*(c2 - c1 - c1 + c0);
2671 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002672
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002673 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2674 SkScalar minT = 0;
2675 SkScalar maxT = SK_Scalar1;
2676 SkScalar mid;
2677 int i;
2678 for (i = 0; i < 16; i++) {
2679 mid = SkScalarAve(minT, maxT);
2680 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2681 if (delta < 0) {
2682 minT = mid;
2683 delta = -delta;
2684 } else {
2685 maxT = mid;
2686 }
2687 if (delta < TOLERANCE) {
2688 break;
2689 }
2690 }
2691 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002692}
2693
2694template <size_t N> static void find_minmax(const SkPoint pts[],
2695 SkScalar* minPtr, SkScalar* maxPtr) {
2696 SkScalar min, max;
2697 min = max = pts[0].fX;
2698 for (size_t i = 1; i < N; ++i) {
2699 min = SkMinScalar(min, pts[i].fX);
2700 max = SkMaxScalar(max, pts[i].fX);
2701 }
2702 *minPtr = min;
2703 *maxPtr = max;
2704}
2705
2706static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2707 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002708
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002709 int dir = 1;
2710 if (pts[0].fY > pts[3].fY) {
2711 storage[0] = pts[3];
2712 storage[1] = pts[2];
2713 storage[2] = pts[1];
2714 storage[3] = pts[0];
2715 pts = storage;
2716 dir = -1;
2717 }
2718 if (y < pts[0].fY || y >= pts[3].fY) {
2719 return 0;
2720 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002721
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002722 // quickreject or quickaccept
2723 SkScalar min, max;
2724 find_minmax<4>(pts, &min, &max);
2725 if (x < min) {
2726 return 0;
2727 }
2728 if (x > max) {
2729 return dir;
2730 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002731
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002732 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002733 SkScalar t;
2734 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2735 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 +00002736 return xt < x ? dir : 0;
2737}
2738
2739static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2740 SkPoint dst[10];
2741 int n = SkChopCubicAtYExtrema(pts, dst);
2742 int w = 0;
2743 for (int i = 0; i <= n; ++i) {
2744 w += winding_mono_cubic(&dst[i * 3], x, y);
2745 }
2746 return w;
2747}
2748
2749static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2750 SkScalar y0 = pts[0].fY;
2751 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002752
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002753 int dir = 1;
2754 if (y0 > y2) {
2755 SkTSwap(y0, y2);
2756 dir = -1;
2757 }
2758 if (y < y0 || y >= y2) {
2759 return 0;
2760 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 // bounds check on X (not required. is it faster?)
2763#if 0
2764 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2765 return 0;
2766 }
2767#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 SkScalar roots[2];
2770 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2771 2 * (pts[1].fY - pts[0].fY),
2772 pts[0].fY - y,
2773 roots);
2774 SkASSERT(n <= 1);
2775 SkScalar xt;
2776 if (0 == n) {
2777 SkScalar mid = SkScalarAve(y0, y2);
2778 // Need [0] and [2] if dir == 1
2779 // and [2] and [0] if dir == -1
2780 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2781 } else {
2782 SkScalar t = roots[0];
2783 SkScalar C = pts[0].fX;
2784 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2785 SkScalar B = 2 * (pts[1].fX - C);
2786 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2787 }
2788 return xt < x ? dir : 0;
2789}
2790
2791static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2792 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2793 if (y0 == y1) {
2794 return true;
2795 }
2796 if (y0 < y1) {
2797 return y1 <= y2;
2798 } else {
2799 return y1 >= y2;
2800 }
2801}
2802
2803static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2804 SkPoint dst[5];
2805 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002806
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2808 n = SkChopQuadAtYExtrema(pts, dst);
2809 pts = dst;
2810 }
2811 int w = winding_mono_quad(pts, x, y);
2812 if (n > 0) {
2813 w += winding_mono_quad(&pts[2], x, y);
2814 }
2815 return w;
2816}
2817
2818static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2819 SkScalar x0 = pts[0].fX;
2820 SkScalar y0 = pts[0].fY;
2821 SkScalar x1 = pts[1].fX;
2822 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002825
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002826 int dir = 1;
2827 if (y0 > y1) {
2828 SkTSwap(y0, y1);
2829 dir = -1;
2830 }
2831 if (y < y0 || y >= y1) {
2832 return 0;
2833 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002834
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2836 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002837
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 if (SkScalarSignAsInt(cross) == dir) {
2839 dir = 0;
2840 }
2841 return dir;
2842}
2843
reed@google.com4db592c2013-10-30 17:39:43 +00002844static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2845 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2846}
2847
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848bool SkPath::contains(SkScalar x, SkScalar y) const {
2849 bool isInverse = this->isInverseFillType();
2850 if (this->isEmpty()) {
2851 return isInverse;
2852 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002853
reed@google.com4db592c2013-10-30 17:39:43 +00002854 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 return isInverse;
2856 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002857
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858 SkPath::Iter iter(*this, true);
2859 bool done = false;
2860 int w = 0;
2861 do {
2862 SkPoint pts[4];
2863 switch (iter.next(pts, false)) {
2864 case SkPath::kMove_Verb:
2865 case SkPath::kClose_Verb:
2866 break;
2867 case SkPath::kLine_Verb:
2868 w += winding_line(pts, x, y);
2869 break;
2870 case SkPath::kQuad_Verb:
2871 w += winding_quad(pts, x, y);
2872 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002873 case SkPath::kConic_Verb:
2874 SkASSERT(0);
2875 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002876 case SkPath::kCubic_Verb:
2877 w += winding_cubic(pts, x, y);
2878 break;
2879 case SkPath::kDone_Verb:
2880 done = true;
2881 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002882 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002883 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 switch (this->getFillType()) {
2886 case SkPath::kEvenOdd_FillType:
2887 case SkPath::kInverseEvenOdd_FillType:
2888 w &= 1;
2889 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002890 default:
2891 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 }
2893 return SkToBool(w);
2894}