blob: 571452a7d9422242a0298400290fb117f370a5d2 [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
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045class SkAutoDisableDirectionCheck {
46public:
47 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
48 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
49 }
50
51 ~SkAutoDisableDirectionCheck() {
52 fPath->fDirection = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 SkPath::Direction fSaved;
58};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000059#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060
reed@android.com8a1c16f2008-12-17 15:59:43 +000061/* This guy's constructor/destructor bracket a path editing operation. It is
62 used when we know the bounds of the amount we are going to add to the path
63 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000066 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000067 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000068
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000069 It also notes if the path was originally degenerate, and if so, sets
70 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000071 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 */
73class SkAutoPathBoundsUpdate {
74public:
75 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
76 this->init(path);
77 }
78
79 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
80 SkScalar right, SkScalar bottom) {
81 fRect.set(left, top, right, bottom);
82 this->init(path);
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000086 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
87 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 if (fEmpty || fHasValidBounds) {
89 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 }
91 }
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com8a1c16f2008-12-17 15:59:43 +000093private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000094 SkPath* fPath;
95 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000096 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000097 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000099
reed@android.com6b82d1a2009-06-03 02:35:01 +0000100 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 // Cannot use fRect for our bounds unless we know it is sorted
102 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000104 // Mark the path's bounds as dirty if (1) they are, or (2) the path
105 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000108 if (fHasValidBounds && !fEmpty) {
109 joinNoEmptyChecks(&fRect, fPath->getBounds());
110 }
111 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 }
113};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000114#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116////////////////////////////////////////////////////////////////////////////
117
118/*
119 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000120 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
122
123 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 1. If we encounter degenerate segments, remove them
125 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
126 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
127 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128*/
129
130////////////////////////////////////////////////////////////////////////////
131
reed@google.comd335d1d2012-01-12 18:17:11 +0000132// flag to require a moveTo if we begin with something else, like lineTo etc.
133#define INITIAL_LASTMOVETOINDEX_VALUE ~0
134
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000135SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000136 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000138 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000139#endif
140{
141 this->resetFields();
142}
143
144void SkPath::resetFields() {
145 //fPathRef is assumed to have been emptied by the caller.
146 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
147 fFillType = kWinding_FillType;
148 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000149 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000150 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000151
152 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
153 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000154}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000157 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000158 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000159#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000160 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000161#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163}
164
165SkPath::~SkPath() {
166 SkDEBUGCODE(this->validate();)
167}
168
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169SkPath& SkPath::operator=(const SkPath& that) {
170 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172 if (this != &that) {
173 fPathRef.reset(SkRef(that.fPathRef.get()));
174 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000175#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000176 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000177#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 }
179 SkDEBUGCODE(this->validate();)
180 return *this;
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183void SkPath::copyFields(const SkPath& that) {
184 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 fLastMoveToIndex = that.fLastMoveToIndex;
186 fFillType = that.fFillType;
187 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 fConvexity = that.fConvexity;
189 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190}
191
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000192bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000193 // note: don't need to look at isConvex or bounds, since just comparing the
194 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000195
196 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
197 // since it is only a cache of info in the fVerbs, but its a fast way to
198 // notice a difference
199
reed@android.com3abec1d2009-03-02 05:36:20 +0000200 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000201 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000202 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000203}
204
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205void SkPath::swap(SkPath& that) {
206 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207
bungeman@google.coma5809a32013-06-21 15:13:34 +0000208 if (this != &that) {
209 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000210 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
211 SkTSwap<uint8_t>(fFillType, that.fFillType);
212 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000213 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
214 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000215#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000216 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
217#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000218 }
219}
220
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000221static inline bool check_edge_against_rect(const SkPoint& p0,
222 const SkPoint& p1,
223 const SkRect& rect,
224 SkPath::Direction dir) {
225 const SkPoint* edgeBegin;
226 SkVector v;
227 if (SkPath::kCW_Direction == dir) {
228 v = p1 - p0;
229 edgeBegin = &p0;
230 } else {
231 v = p0 - p1;
232 edgeBegin = &p1;
233 }
234 if (v.fX || v.fY) {
235 // check the cross product of v with the vec from edgeBegin to each rect corner
236 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
237 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
238 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
239 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
240 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
241 return false;
242 }
243 }
244 return true;
245}
246
247bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
248 // This only handles non-degenerate convex paths currently.
249 if (kConvex_Convexity != this->getConvexity()) {
250 return false;
251 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000252
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 Direction direction;
254 if (!this->cheapComputeDirection(&direction)) {
255 return false;
256 }
257
258 SkPoint firstPt;
259 SkPoint prevPt;
260 RawIter iter(*this);
261 SkPath::Verb verb;
262 SkPoint pts[4];
263 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000264 SkDEBUGCODE(int segmentCount = 0;)
265 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266
267 while ((verb = iter.next(pts)) != kDone_Verb) {
268 int nextPt = -1;
269 switch (verb) {
270 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 SkDEBUGCODE(++moveCnt);
273 firstPt = prevPt = pts[0];
274 break;
275 case kLine_Verb:
276 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000277 SkASSERT(moveCnt && !closeCount);
278 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 break;
280 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000281 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000282 SkASSERT(moveCnt && !closeCount);
283 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000284 nextPt = 2;
285 break;
286 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkASSERT(moveCnt && !closeCount);
288 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 nextPt = 3;
290 break;
291 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 break;
294 default:
295 SkDEBUGFAIL("unknown verb");
296 }
297 if (-1 != nextPt) {
298 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
299 return false;
300 }
301 prevPt = pts[nextPt];
302 }
303 }
304
305 return check_edge_against_rect(prevPt, firstPt, rect, direction);
306}
307
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000308uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000309 uint32_t genID = fPathRef->genID();
310#ifdef SK_BUILD_FOR_ANDROID
311 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
312 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
313#endif
314 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000315}
djsollen@google.come63793a2012-03-21 15:39:03 +0000316
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000317#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000318const SkPath* SkPath::getSourcePath() const {
319 return fSourcePath;
320}
321
322void SkPath::setSourcePath(const SkPath* path) {
323 fSourcePath = path;
324}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000325#endif
326
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327void SkPath::reset() {
328 SkDEBUGCODE(this->validate();)
329
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000330 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000331 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332}
333
334void SkPath::rewind() {
335 SkDEBUGCODE(this->validate();)
336
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000338 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339}
340
reed@google.com7e6c4d12012-05-10 14:05:43 +0000341bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000342 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000343
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000344 if (2 == verbCount) {
345 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
346 if (kLine_Verb == fPathRef->atVerb(1)) {
347 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000348 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000349 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000350 line[0] = pts[0];
351 line[1] = pts[1];
352 }
353 return true;
354 }
355 }
356 return false;
357}
358
caryclark@google.comf1316942011-07-26 19:54:45 +0000359/*
360 Determines if path is a rect by keeping track of changes in direction
361 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000362
caryclark@google.comf1316942011-07-26 19:54:45 +0000363 The direction is computed such that:
364 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000365 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000366 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000367 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000368
caryclark@google.comf1316942011-07-26 19:54:45 +0000369A rectangle cycles up/right/down/left or up/left/down/right.
370
371The test fails if:
372 The path is closed, and followed by a line.
373 A second move creates a new endpoint.
374 A diagonal line is parsed.
375 There's more than four changes of direction.
376 There's a discontinuity on the line (e.g., a move in the middle)
377 The line reverses direction.
378 The rectangle doesn't complete a cycle.
379 The path contains a quadratic or cubic.
380 The path contains fewer than four points.
381 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000382
caryclark@google.comf1316942011-07-26 19:54:45 +0000383It's OK if the path has:
384 Several colinear line segments composing a rectangle side.
385 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387The direction takes advantage of the corners found since opposite sides
388must travel in opposite directions.
389
390FIXME: Allow colinear quads and cubics to be treated like lines.
391FIXME: If the API passes fill-only, return true if the filled stroke
392 is a rectangle, though the caller failed to close the path.
393 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000394bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
395 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000396 int corners = 0;
397 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000398 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000399 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000400 first.set(0, 0);
401 last.set(0, 0);
402 int firstDirection = 0;
403 int lastDirection = 0;
404 int nextDirection = 0;
405 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000407 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000408 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
409 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000410 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000411 savePts = pts;
412 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000413 autoClose = true;
414 case kLine_Verb: {
415 SkScalar left = last.fX;
416 SkScalar top = last.fY;
417 SkScalar right = pts->fX;
418 SkScalar bottom = pts->fY;
419 ++pts;
420 if (left != right && top != bottom) {
421 return false; // diagonal
422 }
423 if (left == right && top == bottom) {
424 break; // single point on side OK
425 }
426 nextDirection = (left != right) << 0 |
427 (left < right || top < bottom) << 1;
428 if (0 == corners) {
429 firstDirection = nextDirection;
430 first = last;
431 last = pts[-1];
432 corners = 1;
433 closedOrMoved = false;
434 break;
435 }
436 if (closedOrMoved) {
437 return false; // closed followed by a line
438 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000439 if (autoClose && nextDirection == firstDirection) {
440 break; // colinear with first
441 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 closedOrMoved = autoClose;
443 if (lastDirection != nextDirection) {
444 if (++corners > 4) {
445 return false; // too many direction changes
446 }
447 }
448 last = pts[-1];
449 if (lastDirection == nextDirection) {
450 break; // colinear segment
451 }
452 // Possible values for corners are 2, 3, and 4.
453 // When corners == 3, nextDirection opposes firstDirection.
454 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000455 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
457 if ((directionCycle ^ turn) != nextDirection) {
458 return false; // direction didn't follow cycle
459 }
460 break;
461 }
462 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000463 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 case kCubic_Verb:
465 return false; // quadratic, cubic not allowed
466 case kMove_Verb:
467 last = *pts++;
468 closedOrMoved = true;
469 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000470 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000471 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000472 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000474 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 lastDirection = nextDirection;
476 }
477 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000478 bool result = 4 == corners && (first == last || autoClose);
479 if (savePts) {
480 *ptsPtr = savePts;
481 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000482 if (result && isClosed) {
483 *isClosed = autoClose;
484 }
485 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000486 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000487 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000488 return result;
489}
490
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000491bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000492 SkDEBUGCODE(this->validate();)
493 int currVerb = 0;
494 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000495 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
496 if (result && rect) {
497 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000498 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000499 return result;
500}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000501
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000502bool SkPath::isRect(bool* isClosed, Direction* direction) const {
503 SkDEBUGCODE(this->validate();)
504 int currVerb = 0;
505 const SkPoint* pts = fPathRef->points();
506 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000507}
508
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000509bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 SkDEBUGCODE(this->validate();)
511 int currVerb = 0;
512 const SkPoint* pts = fPathRef->points();
513 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000514 Direction testDirs[2];
515 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000516 return false;
517 }
518 const SkPoint* last = pts;
519 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000520 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000521 testRects[0].set(first, SkToS32(last - first));
522 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000523 if (testRects[0].contains(testRects[1])) {
524 if (rects) {
525 rects[0] = testRects[0];
526 rects[1] = testRects[1];
527 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000528 if (dirs) {
529 dirs[0] = testDirs[0];
530 dirs[1] = testDirs[1];
531 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 return true;
533 }
534 if (testRects[1].contains(testRects[0])) {
535 if (rects) {
536 rects[0] = testRects[1];
537 rects[1] = testRects[0];
538 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000539 if (dirs) {
540 dirs[0] = testDirs[1];
541 dirs[1] = testDirs[0];
542 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000543 return true;
544 }
545 }
546 return false;
547}
548
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000549int SkPath::countPoints() const {
550 return fPathRef->countPoints();
551}
552
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000553int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554 SkDEBUGCODE(this->validate();)
555
556 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000557 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000558 int count = SkMin32(max, fPathRef->countPoints());
559 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
560 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000561}
562
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000563SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000564 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
565 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000566 }
567 return SkPoint::Make(0, 0);
568}
569
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000570int SkPath::countVerbs() const {
571 return fPathRef->countVerbs();
572}
573
574static inline void copy_verbs_reverse(uint8_t* inorderDst,
575 const uint8_t* reversedSrc,
576 int count) {
577 for (int i = 0; i < count; ++i) {
578 inorderDst[i] = reversedSrc[~i];
579 }
580}
581
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000582int SkPath::getVerbs(uint8_t dst[], int max) const {
583 SkDEBUGCODE(this->validate();)
584
585 SkASSERT(max >= 0);
586 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000587 int count = SkMin32(max, fPathRef->countVerbs());
588 copy_verbs_reverse(dst, fPathRef->verbs(), count);
589 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000590}
591
reed@google.com294dd7b2011-10-11 11:58:32 +0000592bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 SkDEBUGCODE(this->validate();)
594
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000595 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000596 if (count > 0) {
597 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000598 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000600 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000602 if (lastPt) {
603 lastPt->set(0, 0);
604 }
605 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606}
607
608void SkPath::setLastPt(SkScalar x, SkScalar y) {
609 SkDEBUGCODE(this->validate();)
610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612 if (count == 0) {
613 this->moveTo(x, y);
614 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000615 SkPathRef::Editor ed(&fPathRef);
616 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
618}
619
reed@google.com04863fa2011-05-15 04:08:24 +0000620void SkPath::setConvexity(Convexity c) {
621 if (fConvexity != c) {
622 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000623 }
624}
625
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626//////////////////////////////////////////////////////////////////////////////
627// Construction methods
628
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000629#define DIRTY_AFTER_EDIT \
630 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000631 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000632 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000633 } while (0)
634
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635void SkPath::incReserve(U16CPU inc) {
636 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639}
640
641void SkPath::moveTo(SkScalar x, SkScalar y) {
642 SkDEBUGCODE(this->validate();)
643
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645
reed@google.comd335d1d2012-01-12 18:17:11 +0000646 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000647 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000648
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650}
651
652void SkPath::rMoveTo(SkScalar x, SkScalar y) {
653 SkPoint pt;
654 this->getLastPt(&pt);
655 this->moveTo(pt.fX + x, pt.fY + y);
656}
657
reed@google.comd335d1d2012-01-12 18:17:11 +0000658void SkPath::injectMoveToIfNeeded() {
659 if (fLastMoveToIndex < 0) {
660 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000662 x = y = 0;
663 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000665 x = pt.fX;
666 y = pt.fY;
667 }
668 this->moveTo(x, y);
669 }
670}
671
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672void SkPath::lineTo(SkScalar x, SkScalar y) {
673 SkDEBUGCODE(this->validate();)
674
reed@google.comd335d1d2012-01-12 18:17:11 +0000675 this->injectMoveToIfNeeded();
676
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 SkPathRef::Editor ed(&fPathRef);
678 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000679 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680
reed@google.comb54455e2011-05-16 14:16:04 +0000681 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682}
683
684void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000685 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkPoint pt;
687 this->getLastPt(&pt);
688 this->lineTo(pt.fX + x, pt.fY + y);
689}
690
691void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
692 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000693
reed@google.comd335d1d2012-01-12 18:17:11 +0000694 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000695
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000696 SkPathRef::Editor ed(&fPathRef);
697 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 pts[0].set(x1, y1);
699 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000700 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000701
reed@google.comb54455e2011-05-16 14:16:04 +0000702 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703}
704
705void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000706 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707 SkPoint pt;
708 this->getLastPt(&pt);
709 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
710}
711
reed@google.com277c3f82013-05-31 15:17:50 +0000712void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
713 SkScalar w) {
714 // check for <= 0 or NaN with this test
715 if (!(w > 0)) {
716 this->lineTo(x2, y2);
717 } else if (!SkScalarIsFinite(w)) {
718 this->lineTo(x1, y1);
719 this->lineTo(x2, y2);
720 } else if (SK_Scalar1 == w) {
721 this->quadTo(x1, y1, x2, y2);
722 } else {
723 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000724
reed@google.com277c3f82013-05-31 15:17:50 +0000725 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000726
reed@google.com277c3f82013-05-31 15:17:50 +0000727 SkPathRef::Editor ed(&fPathRef);
728 SkPoint* pts = ed.growForConic(w);
729 pts[0].set(x1, y1);
730 pts[1].set(x2, y2);
731 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000732
reed@google.com277c3f82013-05-31 15:17:50 +0000733 DIRTY_AFTER_EDIT;
734 }
735}
736
737void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
738 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000739 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000740 SkPoint pt;
741 this->getLastPt(&pt);
742 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
743}
744
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
746 SkScalar x3, SkScalar y3) {
747 SkDEBUGCODE(this->validate();)
748
reed@google.comd335d1d2012-01-12 18:17:11 +0000749 this->injectMoveToIfNeeded();
750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 SkPathRef::Editor ed(&fPathRef);
752 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753 pts[0].set(x1, y1);
754 pts[1].set(x2, y2);
755 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000756 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757
reed@google.comb54455e2011-05-16 14:16:04 +0000758 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759}
760
761void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
762 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000763 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 SkPoint pt;
765 this->getLastPt(&pt);
766 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
767 pt.fX + x3, pt.fY + y3);
768}
769
770void SkPath::close() {
771 SkDEBUGCODE(this->validate();)
772
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000773 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000775 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 case kLine_Verb:
777 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000778 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000780 case kMove_Verb: {
781 SkPathRef::Editor ed(&fPathRef);
782 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000784 }
reed@google.com277c3f82013-05-31 15:17:50 +0000785 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000786 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000787 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000788 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000789 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000790 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 }
792 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000793
794 // signal that we need a moveTo to follow us (unless we're done)
795#if 0
796 if (fLastMoveToIndex >= 0) {
797 fLastMoveToIndex = ~fLastMoveToIndex;
798 }
799#else
800 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
801#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802}
803
804///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000805
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000806static void assert_known_direction(int dir) {
807 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
808}
809
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810void SkPath::addRect(const SkRect& rect, Direction dir) {
811 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
812}
813
814void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
815 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000816 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000817 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
818 SkAutoDisableDirectionCheck addc(this);
819
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
821
822 this->incReserve(5);
823
824 this->moveTo(left, top);
825 if (dir == kCCW_Direction) {
826 this->lineTo(left, bottom);
827 this->lineTo(right, bottom);
828 this->lineTo(right, top);
829 } else {
830 this->lineTo(right, top);
831 this->lineTo(right, bottom);
832 this->lineTo(left, bottom);
833 }
834 this->close();
835}
836
reed@google.com744faba2012-05-29 19:54:52 +0000837void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
838 SkDEBUGCODE(this->validate();)
839 if (count <= 0) {
840 return;
841 }
842
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000843 SkPathRef::Editor ed(&fPathRef);
844 fLastMoveToIndex = ed.pathRef()->countPoints();
845 uint8_t* vb;
846 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000847 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000849
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000850 memcpy(p, pts, count * sizeof(SkPoint));
851 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000852 if (count > 1) {
853 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
854 // be 0, the compiler will remove the test/branch entirely.
855 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000857 } else {
858 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000859 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000860 }
861 }
862 fSegmentMask |= kLine_SegmentMask;
863 }
864 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000866 }
867
reed@google.com744faba2012-05-29 19:54:52 +0000868 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000870}
871
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000872#include "SkGeometry.h"
873
874static int build_arc_points(const SkRect& oval, SkScalar startAngle,
875 SkScalar sweepAngle,
876 SkPoint pts[kSkBuildQuadArcStorage]) {
877
878 if (0 == sweepAngle &&
879 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
880 // Chrome uses this path to move into and out of ovals. If not
881 // treated as a special case the moves can distort the oval's
882 // bounding box (and break the circle special case).
883 pts[0].set(oval.fRight, oval.centerY());
884 return 1;
885 } else if (0 == oval.width() && 0 == oval.height()) {
886 // Chrome will sometimes create 0 radius round rects. Having degenerate
887 // quad segments in the path prevents the path from being recognized as
888 // a rect.
889 // TODO: optimizing the case where only one of width or height is zero
890 // should also be considered. This case, however, doesn't seem to be
891 // as common as the single point case.
892 pts[0].set(oval.fRight, oval.fTop);
893 return 1;
894 }
895
896 SkVector start, stop;
897
898 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
899 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
900 &stop.fX);
901
902 /* If the sweep angle is nearly (but less than) 360, then due to precision
903 loss in radians-conversion and/or sin/cos, we may end up with coincident
904 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
905 of drawing a nearly complete circle (good).
906 e.g. canvas.drawArc(0, 359.99, ...)
907 -vs- canvas.drawArc(0, 359.9, ...)
908 We try to detect this edge case, and tweak the stop vector
909 */
910 if (start == stop) {
911 SkScalar sw = SkScalarAbs(sweepAngle);
912 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
913 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
914 // make a guess at a tiny angle (in radians) to tweak by
915 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
916 // not sure how much will be enough, so we use a loop
917 do {
918 stopRad -= deltaRad;
919 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
920 } while (start == stop);
921 }
922 }
923
924 SkMatrix matrix;
925
926 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
927 matrix.postTranslate(oval.centerX(), oval.centerY());
928
929 return SkBuildQuadArc(start, stop,
930 sweepAngle > 0 ? kCW_SkRotationDirection :
931 kCCW_SkRotationDirection,
932 &matrix, pts);
933}
934
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000935void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000937 SkRRect rrect;
938 rrect.setRectRadii(rect, (const SkVector*) radii);
939 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000940}
941
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000942/* The inline clockwise and counterclockwise round rect quad approximations
943 make it easier to see the symmetry patterns used by add corner quads.
944Clockwise corner value
945 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
946 path->quadTo(rect.fLeft, rect.fTop + offPtY,
947 rect.fLeft + midPtX, rect.fTop + midPtY);
948 path->quadTo(rect.fLeft + offPtX, rect.fTop,
949 rect.fLeft + rx, rect.fTop);
950
951 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
952 path->quadTo(rect.fRight - offPtX, rect.fTop,
953 rect.fRight - midPtX, rect.fTop + midPtY);
954 path->quadTo(rect.fRight, rect.fTop + offPtY,
955 rect.fRight, rect.fTop + ry);
956
957 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
958 path->quadTo(rect.fRight, rect.fBottom - offPtY,
959 rect.fRight - midPtX, rect.fBottom - midPtY);
960 path->quadTo(rect.fRight - offPtX, rect.fBottom,
961 rect.fRight - rx, rect.fBottom);
962
963 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
964 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
965 rect.fLeft + midPtX, rect.fBottom - midPtY);
966 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
967 rect.fLeft, rect.fBottom - ry);
968
969Counterclockwise
970 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
971 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
972 rect.fLeft + midPtX, rect.fBottom - midPtY);
973 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
974 rect.fLeft + rx, rect.fBottom);
975
976 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
977 path->quadTo(rect.fRight - offPtX, rect.fBottom,
978 rect.fRight - midPtX, rect.fBottom - midPtY);
979 path->quadTo(rect.fRight, rect.fBottom - offPtY,
980 rect.fRight, rect.fBottom - ry);
981
982 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
983 path->quadTo(rect.fRight, rect.fTop + offPtY,
984 rect.fRight - midPtX, rect.fTop + midPtY);
985 path->quadTo(rect.fRight - offPtX, rect.fTop,
986 rect.fRight - rx, rect.fTop);
987
988 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
989 path->quadTo(rect.fLeft + offPtX, rect.fTop,
990 rect.fLeft + midPtX, rect.fTop + midPtY);
991 path->quadTo(rect.fLeft, rect.fTop + offPtY,
992 rect.fLeft, rect.fTop + ry);
993*/
994static void add_corner_quads(SkPath* path, const SkRRect& rrect,
995 SkRRect::Corner corner, SkPath::Direction dir) {
996 const SkRect& rect = rrect.rect();
997 const SkVector& radii = rrect.radii(corner);
998 SkScalar rx = radii.fX;
999 SkScalar ry = radii.fY;
1000 // The mid point of the quadratic arc approximation is half way between the two
1001 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001002 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1003 SkScalar midPtX = rx * mid;
1004 SkScalar midPtY = ry * mid;
1005 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1006 SkScalar offPtX = rx * control;
1007 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001008 static const int kCornerPts = 5;
1009 SkScalar xOff[kCornerPts];
1010 SkScalar yOff[kCornerPts];
1011
1012 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1013 SkASSERT(dir == SkPath::kCCW_Direction
1014 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1015 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1016 xOff[0] = xOff[1] = 0;
1017 xOff[2] = midPtX;
1018 xOff[3] = offPtX;
1019 xOff[4] = rx;
1020 yOff[0] = ry;
1021 yOff[1] = offPtY;
1022 yOff[2] = midPtY;
1023 yOff[3] = yOff[4] = 0;
1024 } else {
1025 xOff[0] = rx;
1026 xOff[1] = offPtX;
1027 xOff[2] = midPtX;
1028 xOff[3] = xOff[4] = 0;
1029 yOff[0] = yOff[1] = 0;
1030 yOff[2] = midPtY;
1031 yOff[3] = offPtY;
1032 yOff[4] = ry;
1033 }
1034 if ((corner - 1) & 2) {
1035 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1036 for (int i = 0; i < kCornerPts; ++i) {
1037 xOff[i] = rect.fLeft + xOff[i];
1038 }
1039 } else {
1040 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1041 for (int i = 0; i < kCornerPts; ++i) {
1042 xOff[i] = rect.fRight - xOff[i];
1043 }
1044 }
1045 if (corner < SkRRect::kLowerRight_Corner) {
1046 for (int i = 0; i < kCornerPts; ++i) {
1047 yOff[i] = rect.fTop + yOff[i];
1048 }
1049 } else {
1050 for (int i = 0; i < kCornerPts; ++i) {
1051 yOff[i] = rect.fBottom - yOff[i];
1052 }
1053 }
1054
1055 SkPoint lastPt;
1056 SkAssertResult(path->getLastPt(&lastPt));
1057 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1058 path->lineTo(xOff[0], yOff[0]);
1059 }
1060 if (rx || ry) {
1061 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1062 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1063 } else {
1064 path->lineTo(xOff[2], yOff[2]);
1065 path->lineTo(xOff[4], yOff[4]);
1066 }
1067}
1068
reed@google.com4ed0fb72012-12-12 20:48:18 +00001069void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001070 assert_known_direction(dir);
1071
1072 if (rrect.isEmpty()) {
1073 return;
1074 }
1075
reed@google.com4ed0fb72012-12-12 20:48:18 +00001076 const SkRect& bounds = rrect.getBounds();
1077
1078 if (rrect.isRect()) {
1079 this->addRect(bounds, dir);
1080 } else if (rrect.isOval()) {
1081 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001082#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001083 } else if (rrect.isSimple()) {
1084 const SkVector& rad = rrect.getSimpleRadii();
1085 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001086#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001087 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001088 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001089
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001090 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001091 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001092
1093 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001094 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001095 this->moveTo(bounds.fLeft,
1096 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1097 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1098 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1099 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1100 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001101 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102 this->moveTo(bounds.fLeft,
1103 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1104 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1105 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1106 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1107 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108 }
1109 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001110 }
1111}
1112
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001113bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001114 int count = fPathRef->countVerbs();
1115 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1116 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001117 if (*verbs == kLine_Verb ||
1118 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001119 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001120 *verbs == kCubic_Verb) {
1121 return false;
1122 }
1123 ++verbs;
1124 }
1125 return true;
1126}
1127
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001128#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001129#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001130#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001131
1132void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1133 Direction dir) {
1134 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001135
humper@google.com75e3ca12013-04-08 21:44:11 +00001136 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001137 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001138 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001139 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001140 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1141 return;
1142 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001143
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001144#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001145 SkScalar w = rect.width();
1146 SkScalar halfW = SkScalarHalf(w);
1147 SkScalar h = rect.height();
1148 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001149
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001150 if (halfW <= 0 || halfH <= 0) {
1151 return;
1152 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001153
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001154 bool skip_hori = rx >= halfW;
1155 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001156
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001157 if (skip_hori && skip_vert) {
1158 this->addOval(rect, dir);
1159 return;
1160 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001161
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001162 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001163
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001164 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001165 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001166
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001167 if (skip_hori) {
1168 rx = halfW;
1169 } else if (skip_vert) {
1170 ry = halfH;
1171 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1173 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001174
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001175 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001176 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001177 if (dir == kCCW_Direction) {
1178 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001179 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001180 }
1181 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1182 rect.fLeft, rect.fTop + ry - sy,
1183 rect.fLeft, rect.fTop + ry); // top-left
1184 if (!skip_vert) {
1185 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1186 }
1187 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1188 rect.fLeft + rx - sx, rect.fBottom,
1189 rect.fLeft + rx, rect.fBottom); // bot-left
1190 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001191 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001192 }
1193 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1194 rect.fRight, rect.fBottom - ry + sy,
1195 rect.fRight, rect.fBottom - ry); // bot-right
1196 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 }
1199 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1200 rect.fRight - rx + sx, rect.fTop,
1201 rect.fRight - rx, rect.fTop); // top-right
1202 } else {
1203 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1204 rect.fRight, rect.fTop + ry - sy,
1205 rect.fRight, rect.fTop + ry); // top-right
1206 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001207 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001208 }
1209 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1210 rect.fRight - rx + sx, rect.fBottom,
1211 rect.fRight - rx, rect.fBottom); // bot-right
1212 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001213 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001214 }
1215 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1216 rect.fLeft, rect.fBottom - ry + sy,
1217 rect.fLeft, rect.fBottom - ry); // bot-left
1218 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001219 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001220 }
1221 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1222 rect.fLeft + rx - sx, rect.fTop,
1223 rect.fLeft + rx, rect.fTop); // top-left
1224 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001225 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001226 }
1227 }
1228 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001229#else
1230 SkRRect rrect;
1231 rrect.setRectXY(rect, rx, ry);
1232 this->addRRect(rrect, dir);
1233#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001234}
1235
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001237 assert_known_direction(dir);
1238
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001239 /* If addOval() is called after previous moveTo(),
1240 this path is still marked as an oval. This is used to
1241 fit into WebKit's calling sequences.
1242 We can't simply check isEmpty() in this case, as additional
1243 moveTo() would mark the path non empty.
1244 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001245 bool isOval = hasOnlyMoveTos();
1246 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001247 fDirection = dir;
1248 } else {
1249 fDirection = kUnknown_Direction;
1250 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001251
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001252 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254 SkAutoPathBoundsUpdate apbu(this, oval);
1255
1256 SkScalar cx = oval.centerX();
1257 SkScalar cy = oval.centerY();
1258 SkScalar rx = SkScalarHalf(oval.width());
1259 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1262 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1263 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1264 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1265
1266 /*
1267 To handle imprecision in computing the center and radii, we revert to
1268 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1269 to ensure that we don't exceed the oval's bounds *ever*, since we want
1270 to use oval for our fast-bounds, rather than have to recompute it.
1271 */
1272 const SkScalar L = oval.fLeft; // cx - rx
1273 const SkScalar T = oval.fTop; // cy - ry
1274 const SkScalar R = oval.fRight; // cx + rx
1275 const SkScalar B = oval.fBottom; // cy + ry
1276
1277 this->incReserve(17); // 8 quads + close
1278 this->moveTo(R, cy);
1279 if (dir == kCCW_Direction) {
1280 this->quadTo( R, cy - sy, cx + mx, cy - my);
1281 this->quadTo(cx + sx, T, cx , T);
1282 this->quadTo(cx - sx, T, cx - mx, cy - my);
1283 this->quadTo( L, cy - sy, L, cy );
1284 this->quadTo( L, cy + sy, cx - mx, cy + my);
1285 this->quadTo(cx - sx, B, cx , B);
1286 this->quadTo(cx + sx, B, cx + mx, cy + my);
1287 this->quadTo( R, cy + sy, R, cy );
1288 } else {
1289 this->quadTo( R, cy + sy, cx + mx, cy + my);
1290 this->quadTo(cx + sx, B, cx , B);
1291 this->quadTo(cx - sx, B, cx - mx, cy + my);
1292 this->quadTo( L, cy + sy, L, cy );
1293 this->quadTo( L, cy - sy, cx - mx, cy - my);
1294 this->quadTo(cx - sx, T, cx , T);
1295 this->quadTo(cx + sx, T, cx + mx, cy - my);
1296 this->quadTo( R, cy - sy, R, cy );
1297 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299
robertphillips@google.com466310d2013-12-03 16:43:54 +00001300 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001301
robertphillips@google.com466310d2013-12-03 16:43:54 +00001302 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001303}
1304
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1306 if (r > 0) {
1307 SkRect rect;
1308 rect.set(x - r, y - r, x + r, y + r);
1309 this->addOval(rect, dir);
1310 }
1311}
1312
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1314 bool forceMoveTo) {
1315 if (oval.width() < 0 || oval.height() < 0) {
1316 return;
1317 }
1318
1319 SkPoint pts[kSkBuildQuadArcStorage];
1320 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1321 SkASSERT((count & 1) == 1);
1322
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001323 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 forceMoveTo = true;
1325 }
1326 this->incReserve(count);
1327 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1328 for (int i = 1; i < count; i += 2) {
1329 this->quadTo(pts[i], pts[i+1]);
1330 }
1331}
1332
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001333void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 if (oval.isEmpty() || 0 == sweepAngle) {
1335 return;
1336 }
1337
1338 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1339
1340 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1341 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1342 return;
1343 }
1344
1345 SkPoint pts[kSkBuildQuadArcStorage];
1346 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1347
1348 this->incReserve(count);
1349 this->moveTo(pts[0]);
1350 for (int i = 1; i < count; i += 2) {
1351 this->quadTo(pts[i], pts[i+1]);
1352 }
1353}
1354
1355/*
1356 Need to handle the case when the angle is sharp, and our computed end-points
1357 for the arc go behind pt1 and/or p2...
1358*/
1359void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1360 SkScalar radius) {
1361 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001362
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 // need to know our prev pt so we can construct tangent vectors
1364 {
1365 SkPoint start;
1366 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001367 // Handle degenerate cases by adding a line to the first point and
1368 // bailing out.
1369 if ((x1 == start.fX && y1 == start.fY) ||
1370 (x1 == x2 && y1 == y2) ||
1371 radius == 0) {
1372 this->lineTo(x1, y1);
1373 return;
1374 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375 before.setNormalize(x1 - start.fX, y1 - start.fY);
1376 after.setNormalize(x2 - x1, y2 - y1);
1377 }
reed@google.comabf15c12011-01-18 20:35:51 +00001378
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379 SkScalar cosh = SkPoint::DotProduct(before, after);
1380 SkScalar sinh = SkPoint::CrossProduct(before, after);
1381
1382 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001383 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 return;
1385 }
reed@google.comabf15c12011-01-18 20:35:51 +00001386
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1388 if (dist < 0) {
1389 dist = -dist;
1390 }
1391
1392 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1393 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1394 SkRotationDirection arcDir;
1395
1396 // now turn before/after into normals
1397 if (sinh > 0) {
1398 before.rotateCCW();
1399 after.rotateCCW();
1400 arcDir = kCW_SkRotationDirection;
1401 } else {
1402 before.rotateCW();
1403 after.rotateCW();
1404 arcDir = kCCW_SkRotationDirection;
1405 }
1406
1407 SkMatrix matrix;
1408 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001409
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 matrix.setScale(radius, radius);
1411 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1412 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001415
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416 this->incReserve(count);
1417 // [xx,yy] == pts[0]
1418 this->lineTo(xx, yy);
1419 for (int i = 1; i < count; i += 2) {
1420 this->quadTo(pts[i], pts[i+1]);
1421 }
1422}
1423
1424///////////////////////////////////////////////////////////////////////////////
1425
1426void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1427 SkMatrix matrix;
1428
1429 matrix.setTranslate(dx, dy);
1430 this->addPath(path, matrix);
1431}
1432
1433void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001434 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001436 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 SkPoint pts[4];
1438 Verb verb;
1439
1440 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1441
1442 while ((verb = iter.next(pts)) != kDone_Verb) {
1443 switch (verb) {
1444 case kMove_Verb:
1445 proc(matrix, &pts[0], &pts[0], 1);
1446 this->moveTo(pts[0]);
1447 break;
1448 case kLine_Verb:
1449 proc(matrix, &pts[1], &pts[1], 1);
1450 this->lineTo(pts[1]);
1451 break;
1452 case kQuad_Verb:
1453 proc(matrix, &pts[1], &pts[1], 2);
1454 this->quadTo(pts[1], pts[2]);
1455 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001456 case kConic_Verb:
1457 proc(matrix, &pts[1], &pts[1], 2);
1458 this->conicTo(pts[1], pts[2], iter.conicWeight());
1459 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 case kCubic_Verb:
1461 proc(matrix, &pts[1], &pts[1], 3);
1462 this->cubicTo(pts[1], pts[2], pts[3]);
1463 break;
1464 case kClose_Verb:
1465 this->close();
1466 break;
1467 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001468 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 }
1470 }
1471}
1472
1473///////////////////////////////////////////////////////////////////////////////
1474
reed@google.com277c3f82013-05-31 15:17:50 +00001475static int pts_in_verb(unsigned verb) {
1476 static const uint8_t gPtsInVerb[] = {
1477 1, // kMove
1478 1, // kLine
1479 2, // kQuad
1480 2, // kConic
1481 3, // kCubic
1482 0, // kClose
1483 0 // kDone
1484 };
1485
1486 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1487 return gPtsInVerb[verb];
1488}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490// ignore the last point of the 1st contour
1491void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001492 int i, vcount = path.fPathRef->countVerbs();
1493 // exit early if the path is empty, or just has a moveTo.
1494 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 return;
1496 }
1497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001500 const uint8_t* verbs = path.fPathRef->verbs();
1501 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001502 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001504 SkASSERT(verbs[~0] == kMove_Verb);
1505 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001506 unsigned v = verbs[~i];
1507 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 if (n == 0) {
1509 break;
1510 }
1511 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001512 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 }
1514
1515 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001516 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 case kLine_Verb:
1518 this->lineTo(pts[-1].fX, pts[-1].fY);
1519 break;
1520 case kQuad_Verb:
1521 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1522 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001523 case kConic_Verb:
1524 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1525 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 case kCubic_Verb:
1527 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1528 pts[-3].fX, pts[-3].fY);
1529 break;
1530 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001531 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 break;
1533 }
reed@google.com277c3f82013-05-31 15:17:50 +00001534 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 }
1536}
1537
reed@google.com63d73742012-01-10 15:33:12 +00001538void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001540
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001541 const SkPoint* pts = src.fPathRef->pointsEnd();
1542 // we will iterator through src's verbs backwards
1543 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1544 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001545 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001546
1547 bool needMove = true;
1548 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001549 while (verbs < verbsEnd) {
1550 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001551 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001552
1553 if (needMove) {
1554 --pts;
1555 this->moveTo(pts->fX, pts->fY);
1556 needMove = false;
1557 }
1558 pts -= n;
1559 switch (v) {
1560 case kMove_Verb:
1561 if (needClose) {
1562 this->close();
1563 needClose = false;
1564 }
1565 needMove = true;
1566 pts += 1; // so we see the point in "if (needMove)" above
1567 break;
1568 case kLine_Verb:
1569 this->lineTo(pts[0]);
1570 break;
1571 case kQuad_Verb:
1572 this->quadTo(pts[1], pts[0]);
1573 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001574 case kConic_Verb:
1575 this->conicTo(pts[1], pts[0], *--conicWeights);
1576 break;
reed@google.com63d73742012-01-10 15:33:12 +00001577 case kCubic_Verb:
1578 this->cubicTo(pts[2], pts[1], pts[0]);
1579 break;
1580 case kClose_Verb:
1581 needClose = true;
1582 break;
1583 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001584 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001585 }
1586 }
1587}
1588
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589///////////////////////////////////////////////////////////////////////////////
1590
1591void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1592 SkMatrix matrix;
1593
1594 matrix.setTranslate(dx, dy);
1595 this->transform(matrix, dst);
1596}
1597
1598#include "SkGeometry.h"
1599
1600static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1601 int level = 2) {
1602 if (--level >= 0) {
1603 SkPoint tmp[5];
1604
1605 SkChopQuadAtHalf(pts, tmp);
1606 subdivide_quad_to(path, &tmp[0], level);
1607 subdivide_quad_to(path, &tmp[2], level);
1608 } else {
1609 path->quadTo(pts[1], pts[2]);
1610 }
1611}
1612
1613static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1614 int level = 2) {
1615 if (--level >= 0) {
1616 SkPoint tmp[7];
1617
1618 SkChopCubicAtHalf(pts, tmp);
1619 subdivide_cubic_to(path, &tmp[0], level);
1620 subdivide_cubic_to(path, &tmp[3], level);
1621 } else {
1622 path->cubicTo(pts[1], pts[2], pts[3]);
1623 }
1624}
1625
1626void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1627 SkDEBUGCODE(this->validate();)
1628 if (dst == NULL) {
1629 dst = (SkPath*)this;
1630 }
1631
tomhudson@google.com8d430182011-06-06 19:11:19 +00001632 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 SkPath tmp;
1634 tmp.fFillType = fFillType;
1635
1636 SkPath::Iter iter(*this, false);
1637 SkPoint pts[4];
1638 SkPath::Verb verb;
1639
reed@google.com4a3b7142012-05-16 17:16:46 +00001640 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 switch (verb) {
1642 case kMove_Verb:
1643 tmp.moveTo(pts[0]);
1644 break;
1645 case kLine_Verb:
1646 tmp.lineTo(pts[1]);
1647 break;
1648 case kQuad_Verb:
1649 subdivide_quad_to(&tmp, pts);
1650 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001651 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001652 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001653 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1654 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 case kCubic_Verb:
1656 subdivide_cubic_to(&tmp, pts);
1657 break;
1658 case kClose_Verb:
1659 tmp.close();
1660 break;
1661 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001662 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 break;
1664 }
1665 }
1666
1667 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 SkPathRef::Editor ed(&dst->fPathRef);
1669 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001670 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1673
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001676 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001677 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001679
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001680 if (kUnknown_Direction == fDirection) {
1681 dst->fDirection = kUnknown_Direction;
1682 } else {
1683 SkScalar det2x2 =
1684 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1685 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1686 if (det2x2 < 0) {
1687 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1688 } else if (det2x2 > 0) {
1689 dst->fDirection = fDirection;
1690 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001691 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001692 dst->fDirection = kUnknown_Direction;
1693 }
1694 }
1695
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 SkDEBUGCODE(dst->validate();)
1697 }
1698}
1699
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700///////////////////////////////////////////////////////////////////////////////
1701///////////////////////////////////////////////////////////////////////////////
1702
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001703enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001704 kEmptyContour_SegmentState, // The current contour is empty. We may be
1705 // starting processing or we may have just
1706 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001707 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1708 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1709 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710};
1711
1712SkPath::Iter::Iter() {
1713#ifdef SK_DEBUG
1714 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001715 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001717 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001718 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719#endif
1720 // need to init enough to make next() harmlessly return kDone_Verb
1721 fVerbs = NULL;
1722 fVerbStop = NULL;
1723 fNeedClose = false;
1724}
1725
1726SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1727 this->setPath(path, forceClose);
1728}
1729
1730void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001731 fPts = path.fPathRef->points();
1732 fVerbs = path.fPathRef->verbs();
1733 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001734 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001736 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 fForceClose = SkToU8(forceClose);
1738 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001739 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740}
1741
1742bool SkPath::Iter::isClosedContour() const {
1743 if (fVerbs == NULL || fVerbs == fVerbStop) {
1744 return false;
1745 }
1746 if (fForceClose) {
1747 return true;
1748 }
1749
1750 const uint8_t* verbs = fVerbs;
1751 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001752
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001753 if (kMove_Verb == *(verbs - 1)) {
1754 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 }
1756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001757 while (verbs > stop) {
1758 // verbs points one beyond the current verb, decrement first.
1759 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 if (kMove_Verb == v) {
1761 break;
1762 }
1763 if (kClose_Verb == v) {
1764 return true;
1765 }
1766 }
1767 return false;
1768}
1769
1770SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001771 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001773 // A special case: if both points are NaN, SkPoint::operation== returns
1774 // false, but the iterator expects that they are treated as the same.
1775 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001776 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1777 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001778 return kClose_Verb;
1779 }
1780
reed@google.com9e25dbf2012-05-15 17:05:38 +00001781 pts[0] = fLastPt;
1782 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 fLastPt = fMoveTo;
1784 fCloseLine = true;
1785 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001786 } else {
1787 pts[0] = fMoveTo;
1788 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790}
1791
reed@google.com9e25dbf2012-05-15 17:05:38 +00001792const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 if (fSegmentState == kAfterMove_SegmentState) {
1794 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001795 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001796 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1799 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001800 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802}
1803
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804void SkPath::Iter::consumeDegenerateSegments() {
1805 // We need to step over anything that will not move the current draw point
1806 // forward before the next move is seen
1807 const uint8_t* lastMoveVerb = 0;
1808 const SkPoint* lastMovePt = 0;
1809 SkPoint lastPt = fLastPt;
1810 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001811 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812 switch (verb) {
1813 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 // Keep a record of this most recent move
1815 lastMoveVerb = fVerbs;
1816 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001817 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001818 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 fPts++;
1820 break;
1821
1822 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001823 // A close when we are in a segment is always valid except when it
1824 // follows a move which follows a segment.
1825 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 return;
1827 }
1828 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001829 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 break;
1831
1832 case kLine_Verb:
1833 if (!IsLineDegenerate(lastPt, fPts[0])) {
1834 if (lastMoveVerb) {
1835 fVerbs = lastMoveVerb;
1836 fPts = lastMovePt;
1837 return;
1838 }
1839 return;
1840 }
1841 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 fPts++;
1844 break;
1845
reed@google.com277c3f82013-05-31 15:17:50 +00001846 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 case kQuad_Verb:
1848 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1849 if (lastMoveVerb) {
1850 fVerbs = lastMoveVerb;
1851 fPts = lastMovePt;
1852 return;
1853 }
1854 return;
1855 }
1856 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001857 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001859 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 break;
1861
1862 case kCubic_Verb:
1863 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1864 if (lastMoveVerb) {
1865 fVerbs = lastMoveVerb;
1866 fPts = lastMovePt;
1867 return;
1868 }
1869 return;
1870 }
1871 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 fPts += 3;
1874 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001875
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001877 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 }
1879 }
1880}
1881
reed@google.com4a3b7142012-05-16 17:16:46 +00001882SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001883 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 // Close the curve if requested and if there is some curve to close
1887 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001888 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889 return kLine_Verb;
1890 }
1891 fNeedClose = false;
1892 return kClose_Verb;
1893 }
1894 return kDone_Verb;
1895 }
1896
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 // fVerbs is one beyond the current verb, decrement first
1898 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899 const SkPoint* SK_RESTRICT srcPts = fPts;
1900 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901
1902 switch (verb) {
1903 case kMove_Verb:
1904 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001905 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 verb = this->autoClose(pts);
1907 if (verb == kClose_Verb) {
1908 fNeedClose = false;
1909 }
1910 return (Verb)verb;
1911 }
1912 if (fVerbs == fVerbStop) { // might be a trailing moveto
1913 return kDone_Verb;
1914 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001915 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001916 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001918 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 fNeedClose = fForceClose;
1921 break;
1922 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 pts[0] = this->cons_moveTo();
1924 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 fLastPt = srcPts[0];
1926 fCloseLine = false;
1927 srcPts += 1;
1928 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001929 case kConic_Verb:
1930 fConicWeights += 1;
1931 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001933 pts[0] = this->cons_moveTo();
1934 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 fLastPt = srcPts[1];
1936 srcPts += 2;
1937 break;
1938 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001939 pts[0] = this->cons_moveTo();
1940 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 fLastPt = srcPts[2];
1942 srcPts += 3;
1943 break;
1944 case kClose_Verb:
1945 verb = this->autoClose(pts);
1946 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 } else {
1949 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001950 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953 break;
1954 }
1955 fPts = srcPts;
1956 return (Verb)verb;
1957}
1958
1959///////////////////////////////////////////////////////////////////////////////
1960
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001961SkPath::RawIter::RawIter() {
1962#ifdef SK_DEBUG
1963 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001964 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001965 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1966#endif
1967 // need to init enough to make next() harmlessly return kDone_Verb
1968 fVerbs = NULL;
1969 fVerbStop = NULL;
1970}
1971
1972SkPath::RawIter::RawIter(const SkPath& path) {
1973 this->setPath(path);
1974}
1975
1976void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001977 fPts = path.fPathRef->points();
1978 fVerbs = path.fPathRef->verbs();
1979 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001980 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001981 fMoveTo.fX = fMoveTo.fY = 0;
1982 fLastPt.fX = fLastPt.fY = 0;
1983}
1984
1985SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001986 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001987 if (fVerbs == fVerbStop) {
1988 return kDone_Verb;
1989 }
1990
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001991 // fVerbs points one beyond next verb so decrement first.
1992 unsigned verb = *(--fVerbs);
1993 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001994
1995 switch (verb) {
1996 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001997 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 fMoveTo = srcPts[0];
1999 fLastPt = fMoveTo;
2000 srcPts += 1;
2001 break;
2002 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002003 pts[0] = fLastPt;
2004 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005 fLastPt = srcPts[0];
2006 srcPts += 1;
2007 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002008 case kConic_Verb:
2009 fConicWeights += 1;
2010 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002011 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002012 pts[0] = fLastPt;
2013 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014 fLastPt = srcPts[1];
2015 srcPts += 2;
2016 break;
2017 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002018 pts[0] = fLastPt;
2019 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002020 fLastPt = srcPts[2];
2021 srcPts += 3;
2022 break;
2023 case kClose_Verb:
2024 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002025 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002026 break;
2027 }
2028 fPts = srcPts;
2029 return (Verb)verb;
2030}
2031
2032///////////////////////////////////////////////////////////////////////////////
2033
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002035 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036*/
2037
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002038size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 SkDEBUGCODE(this->validate();)
2040
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002041 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002042 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043 return SkAlign4(byteCount);
2044 }
2045
2046 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002047
robertphillips@google.com466310d2013-12-03 16:43:54 +00002048 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002049 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002050 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002051 (fDirection << kDirection_SerializationShift)
2052#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002053 | (0x1 << kNewFormat_SerializationShift)
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002054#endif
robertphillips@google.com466310d2013-12-03 16:43:54 +00002055#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
2056 | (0x1 << kNewFormat2_SerializationShift)
2057#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002058 ;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002059
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002060 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002062 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002064 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002065 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066}
2067
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002068size_t SkPath::readFromMemory(const void* storage, size_t length) {
2069 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002070
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002071 int32_t packed;
2072 if (!buffer.readS32(&packed)) {
2073 return 0;
2074 }
2075
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002076 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2077 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002078 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002079 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com466310d2013-12-03 16:43:54 +00002080#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
2081 bool newFormat = (packed >> kNewFormat2_SerializationShift) & 1;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002082#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002084 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer
robertphillips@google.com466310d2013-12-03 16:43:54 +00002085#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002086 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002087#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002088 );
reed@google.comabf15c12011-01-18 20:35:51 +00002089
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002090 size_t sizeRead = 0;
2091 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002092 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002093 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002094 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002095 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002096 } else if (NULL != pathRef) {
2097 // If the buffer is not valid, pathRef should be NULL
2098 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002099 }
2100 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101}
2102
2103///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002104
reed@google.com51bbe752013-01-17 22:07:50 +00002105#include "SkString.h"
2106
2107static void append_scalar(SkString* str, SkScalar value) {
2108 SkString tmp;
2109 tmp.printf("%g", value);
2110 if (tmp.contains('.')) {
2111 tmp.appendUnichar('f');
2112 }
2113 str->append(tmp);
2114}
2115
2116static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002117 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002118 str->append(label);
2119 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002120
reed@google.com51bbe752013-01-17 22:07:50 +00002121 const SkScalar* values = &pts[0].fX;
2122 count *= 2;
2123
2124 for (int i = 0; i < count; ++i) {
2125 append_scalar(str, values[i]);
2126 if (i < count - 1) {
2127 str->append(", ");
2128 }
2129 }
reed@google.com277c3f82013-05-31 15:17:50 +00002130 if (conicWeight >= 0) {
2131 str->append(", ");
2132 append_scalar(str, conicWeight);
2133 }
reed@google.com51bbe752013-01-17 22:07:50 +00002134 str->append(");\n");
2135}
2136
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137void SkPath::dump(bool forceClose, const char title[]) const {
2138 Iter iter(*this, forceClose);
2139 SkPoint pts[4];
2140 Verb verb;
2141
2142 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2143 title ? title : "");
2144
reed@google.com51bbe752013-01-17 22:07:50 +00002145 SkString builder;
2146
reed@google.com4a3b7142012-05-16 17:16:46 +00002147 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 switch (verb) {
2149 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002150 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002153 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
2155 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002156 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002158 case kConic_Verb:
2159 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2160 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002162 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002165 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 default:
2168 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2169 verb = kDone_Verb; // stop the loop
2170 break;
2171 }
2172 }
reed@google.com51bbe752013-01-17 22:07:50 +00002173 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002174}
2175
reed@android.come522ca52009-11-23 20:10:41 +00002176void SkPath::dump() const {
2177 this->dump(false);
2178}
2179
2180#ifdef SK_DEBUG
2181void SkPath::validate() const {
2182 SkASSERT(this != NULL);
2183 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002184
djsollen@google.com077348c2012-10-22 20:23:32 +00002185#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002186 if (!fBoundsIsDirty) {
2187 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002188
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002189 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002190 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002191
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002192 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002193 // if we're empty, fBounds may be empty but translated, so we can't
2194 // necessarily compare to bounds directly
2195 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2196 // be [2, 2, 2, 2]
2197 SkASSERT(bounds.isEmpty());
2198 SkASSERT(fBounds.isEmpty());
2199 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002200 if (bounds.isEmpty()) {
2201 SkASSERT(fBounds.isEmpty());
2202 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002203 if (!fBounds.isEmpty()) {
2204 SkASSERT(fBounds.contains(bounds));
2205 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002206 }
reed@android.come522ca52009-11-23 20:10:41 +00002207 }
2208 }
reed@google.com10296cc2011-09-21 12:29:05 +00002209
2210 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002211 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2212 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2213 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002214 case kLine_Verb:
2215 mask |= kLine_SegmentMask;
2216 break;
2217 case kQuad_Verb:
2218 mask |= kQuad_SegmentMask;
2219 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002220 case kConic_Verb:
2221 mask |= kConic_SegmentMask;
2222 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002223 case kCubic_Verb:
2224 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002225 case kMove_Verb: // these verbs aren't included in the segment mask.
2226 case kClose_Verb:
2227 break;
2228 case kDone_Verb:
2229 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2230 break;
2231 default:
2232 SkDEBUGFAIL("Unknown Verb");
2233 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002234 }
2235 }
2236 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002237#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002238}
djsollen@google.com077348c2012-10-22 20:23:32 +00002239#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002240
reed@google.com04863fa2011-05-15 04:08:24 +00002241///////////////////////////////////////////////////////////////////////////////
2242
reed@google.com0b7b9822011-05-16 12:29:27 +00002243static int sign(SkScalar x) { return x < 0; }
2244#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002245
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002246static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2247 // The error epsilon was empirically derived; worse case round rects
2248 // with a mid point outset by 2x float epsilon in tests had an error
2249 // of 12.
2250 const int epsilon = 16;
2251 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2252 return false;
2253 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002254 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002255 int aBits = SkFloatAs2sCompliment(compA);
2256 int bBits = SkFloatAs2sCompliment(compB);
2257 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002258}
2259
2260// only valid for a single contour
2261struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002262 Convexicator()
2263 : fPtCount(0)
2264 , fConvexity(SkPath::kConvex_Convexity)
2265 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002266 fSign = 0;
2267 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002268 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002269 fCurrPt.set(0, 0);
2270 fVec0.set(0, 0);
2271 fVec1.set(0, 0);
2272 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002273
2274 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002275 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002276 }
2277
2278 SkPath::Convexity getConvexity() const { return fConvexity; }
2279
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280 /** The direction returned is only valid if the path is determined convex */
2281 SkPath::Direction getDirection() const { return fDirection; }
2282
reed@google.com04863fa2011-05-15 04:08:24 +00002283 void addPt(const SkPoint& pt) {
2284 if (SkPath::kConcave_Convexity == fConvexity) {
2285 return;
2286 }
2287
2288 if (0 == fPtCount) {
2289 fCurrPt = pt;
2290 ++fPtCount;
2291 } else {
2292 SkVector vec = pt - fCurrPt;
2293 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002294 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 fCurrPt = pt;
2296 if (++fPtCount == 2) {
2297 fFirstVec = fVec1 = vec;
2298 } else {
2299 SkASSERT(fPtCount > 2);
2300 this->addVec(vec);
2301 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002302
reed@google.com85b6e392011-05-15 20:25:17 +00002303 int sx = sign(vec.fX);
2304 int sy = sign(vec.fY);
2305 fDx += (sx != fSx);
2306 fDy += (sy != fSy);
2307 fSx = sx;
2308 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002309
reed@google.com85b6e392011-05-15 20:25:17 +00002310 if (fDx > 3 || fDy > 3) {
2311 fConvexity = SkPath::kConcave_Convexity;
2312 }
reed@google.com04863fa2011-05-15 04:08:24 +00002313 }
2314 }
2315 }
2316
2317 void close() {
2318 if (fPtCount > 2) {
2319 this->addVec(fFirstVec);
2320 }
2321 }
2322
2323private:
2324 void addVec(const SkVector& vec) {
2325 SkASSERT(vec.fX || vec.fY);
2326 fVec0 = fVec1;
2327 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002328 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2329 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2330 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2331 largest = SkTMax(largest, -smallest);
2332 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002333 if (0 == fSign) {
2334 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002335 if (1 == sign) {
2336 fDirection = SkPath::kCW_Direction;
2337 } else if (-1 == sign) {
2338 fDirection = SkPath::kCCW_Direction;
2339 }
reed@google.com04863fa2011-05-15 04:08:24 +00002340 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002341 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002342 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002343 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002344 }
2345 }
2346 }
2347
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002348 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002349 SkPoint fCurrPt;
2350 SkVector fVec0, fVec1, fFirstVec;
2351 int fPtCount; // non-degenerate points
2352 int fSign;
2353 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002354 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002355 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002356};
2357
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002358SkPath::Convexity SkPath::internalGetConvexity() const {
2359 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002360 SkPoint pts[4];
2361 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002362 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002363
2364 int contourCount = 0;
2365 int count;
2366 Convexicator state;
2367
2368 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2369 switch (verb) {
2370 case kMove_Verb:
2371 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002372 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002373 return kConcave_Convexity;
2374 }
2375 pts[1] = pts[0];
2376 count = 1;
2377 break;
2378 case kLine_Verb: count = 1; break;
2379 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002380 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002381 case kCubic_Verb: count = 3; break;
2382 case kClose_Verb:
2383 state.close();
2384 count = 0;
2385 break;
2386 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002387 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002389 return kConcave_Convexity;
2390 }
2391
2392 for (int i = 1; i <= count; i++) {
2393 state.addPt(pts[i]);
2394 }
2395 // early exit
2396 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002397 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002398 return kConcave_Convexity;
2399 }
2400 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002401 fConvexity = state.getConvexity();
2402 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2403 fDirection = state.getDirection();
2404 }
2405 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002406}
reed@google.com69a99432012-01-10 18:00:10 +00002407
2408///////////////////////////////////////////////////////////////////////////////
2409
2410class ContourIter {
2411public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002412 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002413
2414 bool done() const { return fDone; }
2415 // if !done() then these may be called
2416 int count() const { return fCurrPtCount; }
2417 const SkPoint* pts() const { return fCurrPt; }
2418 void next();
2419
2420private:
2421 int fCurrPtCount;
2422 const SkPoint* fCurrPt;
2423 const uint8_t* fCurrVerb;
2424 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002425 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002426 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002427 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002428};
2429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430ContourIter::ContourIter(const SkPathRef& pathRef) {
2431 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002432 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002433 fCurrPt = pathRef.points();
2434 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002435 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002436 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002437 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002438 this->next();
2439}
2440
2441void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002442 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002443 fDone = true;
2444 }
2445 if (fDone) {
2446 return;
2447 }
2448
2449 // skip pts of prev contour
2450 fCurrPt += fCurrPtCount;
2451
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002452 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002453 int ptCount = 1; // moveTo
2454 const uint8_t* verbs = fCurrVerb;
2455
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002456 for (--verbs; verbs > fStopVerbs; --verbs) {
2457 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002458 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002459 goto CONTOUR_END;
2460 case SkPath::kLine_Verb:
2461 ptCount += 1;
2462 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002463 case SkPath::kConic_Verb:
2464 fCurrConicWeight += 1;
2465 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002466 case SkPath::kQuad_Verb:
2467 ptCount += 2;
2468 break;
2469 case SkPath::kCubic_Verb:
2470 ptCount += 3;
2471 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002472 case SkPath::kClose_Verb:
2473 break;
2474 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002475 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002476 break;
2477 }
2478 }
2479CONTOUR_END:
2480 fCurrPtCount = ptCount;
2481 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002482 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002483}
2484
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002485// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002486static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002487 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2488 // We may get 0 when the above subtracts underflow. We expect this to be
2489 // very rare and lazily promote to double.
2490 if (0 == cross) {
2491 double p0x = SkScalarToDouble(p0.fX);
2492 double p0y = SkScalarToDouble(p0.fY);
2493
2494 double p1x = SkScalarToDouble(p1.fX);
2495 double p1y = SkScalarToDouble(p1.fY);
2496
2497 double p2x = SkScalarToDouble(p2.fX);
2498 double p2y = SkScalarToDouble(p2.fY);
2499
2500 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2501 (p1y - p0y) * (p2x - p0x));
2502
2503 }
2504 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002505}
2506
reed@google.comc1ea60a2012-01-31 15:15:36 +00002507// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002508static int find_max_y(const SkPoint pts[], int count) {
2509 SkASSERT(count > 0);
2510 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002511 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002512 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002513 SkScalar y = pts[i].fY;
2514 if (y > max) {
2515 max = y;
2516 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002517 }
2518 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002519 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002520}
2521
reed@google.comcabaf1d2012-01-11 21:03:05 +00002522static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2523 int i = index;
2524 for (;;) {
2525 i = (i + inc) % n;
2526 if (i == index) { // we wrapped around, so abort
2527 break;
2528 }
2529 if (pts[index] != pts[i]) { // found a different point, success!
2530 break;
2531 }
2532 }
2533 return i;
2534}
2535
reed@google.comc1ea60a2012-01-31 15:15:36 +00002536/**
2537 * Starting at index, and moving forward (incrementing), find the xmin and
2538 * xmax of the contiguous points that have the same Y.
2539 */
2540static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2541 int* maxIndexPtr) {
2542 const SkScalar y = pts[index].fY;
2543 SkScalar min = pts[index].fX;
2544 SkScalar max = min;
2545 int minIndex = index;
2546 int maxIndex = index;
2547 for (int i = index + 1; i < n; ++i) {
2548 if (pts[i].fY != y) {
2549 break;
2550 }
2551 SkScalar x = pts[i].fX;
2552 if (x < min) {
2553 min = x;
2554 minIndex = i;
2555 } else if (x > max) {
2556 max = x;
2557 maxIndex = i;
2558 }
2559 }
2560 *maxIndexPtr = maxIndex;
2561 return minIndex;
2562}
2563
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002564static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002565 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002566}
2567
reed@google.comac8543f2012-01-30 20:51:25 +00002568/*
2569 * We loop through all contours, and keep the computed cross-product of the
2570 * contour that contained the global y-max. If we just look at the first
2571 * contour, we may find one that is wound the opposite way (correctly) since
2572 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2573 * that is outer most (or at least has the global y-max) before we can consider
2574 * its cross product.
2575 */
reed@google.com69a99432012-01-10 18:00:10 +00002576bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002577 if (kUnknown_Direction != fDirection) {
2578 *dir = static_cast<Direction>(fDirection);
2579 return true;
2580 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002581
2582 // don't want to pay the cost for computing this if it
2583 // is unknown, so we don't call isConvex()
2584 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2585 SkASSERT(kUnknown_Direction == fDirection);
2586 *dir = static_cast<Direction>(fDirection);
2587 return false;
2588 }
reed@google.com69a99432012-01-10 18:00:10 +00002589
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002590 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002591
reed@google.comac8543f2012-01-30 20:51:25 +00002592 // initialize with our logical y-min
2593 SkScalar ymax = this->getBounds().fTop;
2594 SkScalar ymaxCross = 0;
2595
reed@google.com69a99432012-01-10 18:00:10 +00002596 for (; !iter.done(); iter.next()) {
2597 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002598 if (n < 3) {
2599 continue;
2600 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002601
reed@google.comcabaf1d2012-01-11 21:03:05 +00002602 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002603 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002604 int index = find_max_y(pts, n);
2605 if (pts[index].fY < ymax) {
2606 continue;
2607 }
2608
2609 // If there is more than 1 distinct point at the y-max, we take the
2610 // x-min and x-max of them and just subtract to compute the dir.
2611 if (pts[(index + 1) % n].fY == pts[index].fY) {
2612 int maxIndex;
2613 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2614 if (minIndex == maxIndex) {
2615 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002616 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002617 SkASSERT(pts[minIndex].fY == pts[index].fY);
2618 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2619 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2620 // we just subtract the indices, and let that auto-convert to
2621 // SkScalar, since we just want - or + to signal the direction.
2622 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002623 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002624 TRY_CROSSPROD:
2625 // Find a next and prev index to use for the cross-product test,
2626 // but we try to find pts that form non-zero vectors from pts[index]
2627 //
2628 // Its possible that we can't find two non-degenerate vectors, so
2629 // we have to guard our search (e.g. all the pts could be in the
2630 // same place).
2631
2632 // we pass n - 1 instead of -1 so we don't foul up % operator by
2633 // passing it a negative LH argument.
2634 int prev = find_diff_pt(pts, index, n, n - 1);
2635 if (prev == index) {
2636 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002637 continue;
2638 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002639 int next = find_diff_pt(pts, index, n, 1);
2640 SkASSERT(next != index);
2641 cross = cross_prod(pts[prev], pts[index], pts[next]);
2642 // if we get a zero and the points are horizontal, then we look at the spread in
2643 // x-direction. We really should continue to walk away from the degeneracy until
2644 // there is a divergence.
2645 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2646 // construct the subtract so we get the correct Direction below
2647 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002648 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002649 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002650
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002651 if (cross) {
2652 // record our best guess so far
2653 ymax = pts[index].fY;
2654 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002655 }
2656 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002657 if (ymaxCross) {
2658 crossToDir(ymaxCross, dir);
2659 fDirection = *dir;
2660 return true;
2661 } else {
2662 return false;
2663 }
reed@google.comac8543f2012-01-30 20:51:25 +00002664}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002665
2666///////////////////////////////////////////////////////////////////////////////
2667
2668static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2669 SkScalar D, SkScalar t) {
2670 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2671}
2672
2673static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2674 SkScalar t) {
2675 SkScalar A = c3 + 3*(c1 - c2) - c0;
2676 SkScalar B = 3*(c2 - c1 - c1 + c0);
2677 SkScalar C = 3*(c1 - c0);
2678 SkScalar D = c0;
2679 return eval_cubic_coeff(A, B, C, D, t);
2680}
2681
2682/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2683 t value such that cubic(t) = target
2684 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002686 SkScalar target, SkScalar* t) {
2687 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2688 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002689
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690 SkScalar D = c0 - target;
2691 SkScalar A = c3 + 3*(c1 - c2) - c0;
2692 SkScalar B = 3*(c2 - c1 - c1 + c0);
2693 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002694
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002695 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2696 SkScalar minT = 0;
2697 SkScalar maxT = SK_Scalar1;
2698 SkScalar mid;
2699 int i;
2700 for (i = 0; i < 16; i++) {
2701 mid = SkScalarAve(minT, maxT);
2702 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2703 if (delta < 0) {
2704 minT = mid;
2705 delta = -delta;
2706 } else {
2707 maxT = mid;
2708 }
2709 if (delta < TOLERANCE) {
2710 break;
2711 }
2712 }
2713 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714}
2715
2716template <size_t N> static void find_minmax(const SkPoint pts[],
2717 SkScalar* minPtr, SkScalar* maxPtr) {
2718 SkScalar min, max;
2719 min = max = pts[0].fX;
2720 for (size_t i = 1; i < N; ++i) {
2721 min = SkMinScalar(min, pts[i].fX);
2722 max = SkMaxScalar(max, pts[i].fX);
2723 }
2724 *minPtr = min;
2725 *maxPtr = max;
2726}
2727
2728static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2729 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731 int dir = 1;
2732 if (pts[0].fY > pts[3].fY) {
2733 storage[0] = pts[3];
2734 storage[1] = pts[2];
2735 storage[2] = pts[1];
2736 storage[3] = pts[0];
2737 pts = storage;
2738 dir = -1;
2739 }
2740 if (y < pts[0].fY || y >= pts[3].fY) {
2741 return 0;
2742 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002743
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002744 // quickreject or quickaccept
2745 SkScalar min, max;
2746 find_minmax<4>(pts, &min, &max);
2747 if (x < min) {
2748 return 0;
2749 }
2750 if (x > max) {
2751 return dir;
2752 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002753
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002754 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002755 SkScalar t;
2756 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2757 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 +00002758 return xt < x ? dir : 0;
2759}
2760
2761static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2762 SkPoint dst[10];
2763 int n = SkChopCubicAtYExtrema(pts, dst);
2764 int w = 0;
2765 for (int i = 0; i <= n; ++i) {
2766 w += winding_mono_cubic(&dst[i * 3], x, y);
2767 }
2768 return w;
2769}
2770
2771static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2772 SkScalar y0 = pts[0].fY;
2773 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775 int dir = 1;
2776 if (y0 > y2) {
2777 SkTSwap(y0, y2);
2778 dir = -1;
2779 }
2780 if (y < y0 || y >= y2) {
2781 return 0;
2782 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002783
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002784 // bounds check on X (not required. is it faster?)
2785#if 0
2786 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2787 return 0;
2788 }
2789#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002790
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 SkScalar roots[2];
2792 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2793 2 * (pts[1].fY - pts[0].fY),
2794 pts[0].fY - y,
2795 roots);
2796 SkASSERT(n <= 1);
2797 SkScalar xt;
2798 if (0 == n) {
2799 SkScalar mid = SkScalarAve(y0, y2);
2800 // Need [0] and [2] if dir == 1
2801 // and [2] and [0] if dir == -1
2802 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2803 } else {
2804 SkScalar t = roots[0];
2805 SkScalar C = pts[0].fX;
2806 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2807 SkScalar B = 2 * (pts[1].fX - C);
2808 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2809 }
2810 return xt < x ? dir : 0;
2811}
2812
2813static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2814 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2815 if (y0 == y1) {
2816 return true;
2817 }
2818 if (y0 < y1) {
2819 return y1 <= y2;
2820 } else {
2821 return y1 >= y2;
2822 }
2823}
2824
2825static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2826 SkPoint dst[5];
2827 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2830 n = SkChopQuadAtYExtrema(pts, dst);
2831 pts = dst;
2832 }
2833 int w = winding_mono_quad(pts, x, y);
2834 if (n > 0) {
2835 w += winding_mono_quad(&pts[2], x, y);
2836 }
2837 return w;
2838}
2839
2840static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2841 SkScalar x0 = pts[0].fX;
2842 SkScalar y0 = pts[0].fY;
2843 SkScalar x1 = pts[1].fX;
2844 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002845
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002847
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 int dir = 1;
2849 if (y0 > y1) {
2850 SkTSwap(y0, y1);
2851 dir = -1;
2852 }
2853 if (y < y0 || y >= y1) {
2854 return 0;
2855 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002856
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2858 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 if (SkScalarSignAsInt(cross) == dir) {
2861 dir = 0;
2862 }
2863 return dir;
2864}
2865
reed@google.com4db592c2013-10-30 17:39:43 +00002866static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2867 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2868}
2869
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870bool SkPath::contains(SkScalar x, SkScalar y) const {
2871 bool isInverse = this->isInverseFillType();
2872 if (this->isEmpty()) {
2873 return isInverse;
2874 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002875
reed@google.com4db592c2013-10-30 17:39:43 +00002876 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 return isInverse;
2878 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 SkPath::Iter iter(*this, true);
2881 bool done = false;
2882 int w = 0;
2883 do {
2884 SkPoint pts[4];
2885 switch (iter.next(pts, false)) {
2886 case SkPath::kMove_Verb:
2887 case SkPath::kClose_Verb:
2888 break;
2889 case SkPath::kLine_Verb:
2890 w += winding_line(pts, x, y);
2891 break;
2892 case SkPath::kQuad_Verb:
2893 w += winding_quad(pts, x, y);
2894 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002895 case SkPath::kConic_Verb:
2896 SkASSERT(0);
2897 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002898 case SkPath::kCubic_Verb:
2899 w += winding_cubic(pts, x, y);
2900 break;
2901 case SkPath::kDone_Verb:
2902 done = true;
2903 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002904 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002905 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002906
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907 switch (this->getFillType()) {
2908 case SkPath::kEvenOdd_FillType:
2909 case SkPath::kInverseEvenOdd_FillType:
2910 w &= 1;
2911 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002912 default:
2913 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 }
2915 return SkToBool(w);
2916}