blob: eaa6c93dec4e3255fdcffc177d85f722d4789fb9 [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
reed@google.com744faba2012-05-29 19:54:52 +000018// This value is just made-up for now. When count is 4, calling memset was much
19// slower than just writing the loop. This seems odd, and hopefully in the
20// future this we appear to have been a fluke...
21#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
22
reed@android.com8a1c16f2008-12-17 15:59:43 +000023////////////////////////////////////////////////////////////////////////////
24
reed@google.com3563c9e2011-11-14 19:34:57 +000025/**
26 * Path.bounds is defined to be the bounds of all the control points.
27 * If we called bounds.join(r) we would skip r if r was empty, which breaks
28 * our promise. Hence we have a custom joiner that doesn't look at emptiness
29 */
30static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
31 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
32 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
33 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
34 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
35}
36
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000037static bool is_degenerate(const SkPath& path) {
38 SkPath::Iter iter(path, false);
39 SkPoint pts[4];
40 return SkPath::kDone_Verb == iter.next(pts);
41}
42
bsalomon@google.com30c174b2012-11-13 14:36:42 +000043class SkAutoDisableDirectionCheck {
44public:
45 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
46 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
47 }
48
49 ~SkAutoDisableDirectionCheck() {
50 fPath->fDirection = fSaved;
51 }
52
53private:
54 SkPath* fPath;
55 SkPath::Direction fSaved;
56};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000057#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059/* This guy's constructor/destructor bracket a path editing operation. It is
60 used when we know the bounds of the amount we are going to add to the path
61 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000062
reed@android.com8a1c16f2008-12-17 15:59:43 +000063 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000064 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000065 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000066
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000067 It also notes if the path was originally degenerate, and if so, sets
68 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000069 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 */
71class SkAutoPathBoundsUpdate {
72public:
73 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
74 this->init(path);
75 }
76
77 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
78 SkScalar right, SkScalar bottom) {
79 fRect.set(left, top, right, bottom);
80 this->init(path);
81 }
reed@google.comabf15c12011-01-18 20:35:51 +000082
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000084 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
85 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000086 if (fEmpty || fHasValidBounds) {
87 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 }
89 }
reed@google.comabf15c12011-01-18 20:35:51 +000090
reed@android.com8a1c16f2008-12-17 15:59:43 +000091private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 SkPath* fPath;
93 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000095 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000096 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000097
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 // Cannot use fRect for our bounds unless we know it is sorted
100 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000102 // Mark the path's bounds as dirty if (1) they are, or (2) the path
103 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000104 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 if (fHasValidBounds && !fEmpty) {
107 joinNoEmptyChecks(&fRect, fPath->getBounds());
108 }
109 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 }
111};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000112#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114////////////////////////////////////////////////////////////////////////////
115
116/*
117 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000118 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
120
121 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000122 1. If we encounter degenerate segments, remove them
123 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
124 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
125 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126*/
127
128////////////////////////////////////////////////////////////////////////////
129
reed@google.comd335d1d2012-01-12 18:17:11 +0000130// flag to require a moveTo if we begin with something else, like lineTo etc.
131#define INITIAL_LASTMOVETOINDEX_VALUE ~0
132
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000133SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000134 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000136 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137#endif
138{
139 this->resetFields();
140}
141
142void SkPath::resetFields() {
143 //fPathRef is assumed to have been emptied by the caller.
144 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
145 fFillType = kWinding_FillType;
146 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000147 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000148 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000149
150 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
151 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000152}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000155 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000157#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000158 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000159#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000173#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000174 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000175#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 }
177 SkDEBUGCODE(this->validate();)
178 return *this;
179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::copyFields(const SkPath& that) {
182 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 fLastMoveToIndex = that.fLastMoveToIndex;
184 fFillType = that.fFillType;
185 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 fConvexity = that.fConvexity;
187 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000193
194 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
195 // since it is only a cache of info in the fVerbs, but its a fast way to
196 // notice a difference
197
reed@android.com3abec1d2009-03-02 05:36:20 +0000198 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000199 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000200 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000201}
202
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203void SkPath::swap(SkPath& that) {
204 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205
bungeman@google.coma5809a32013-06-21 15:13:34 +0000206 if (this != &that) {
207 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000208 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
209 SkTSwap<uint8_t>(fFillType, that.fFillType);
210 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000211 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
212 SkTSwap<uint8_t>(fDirection, that.fDirection);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000213#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000214 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
215#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 }
217}
218
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000219static inline bool check_edge_against_rect(const SkPoint& p0,
220 const SkPoint& p1,
221 const SkRect& rect,
222 SkPath::Direction dir) {
223 const SkPoint* edgeBegin;
224 SkVector v;
225 if (SkPath::kCW_Direction == dir) {
226 v = p1 - p0;
227 edgeBegin = &p0;
228 } else {
229 v = p0 - p1;
230 edgeBegin = &p1;
231 }
232 if (v.fX || v.fY) {
233 // check the cross product of v with the vec from edgeBegin to each rect corner
234 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
235 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
236 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
237 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
238 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
239 return false;
240 }
241 }
242 return true;
243}
244
245bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
246 // This only handles non-degenerate convex paths currently.
247 if (kConvex_Convexity != this->getConvexity()) {
248 return false;
249 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000250
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000251 Direction direction;
252 if (!this->cheapComputeDirection(&direction)) {
253 return false;
254 }
255
256 SkPoint firstPt;
257 SkPoint prevPt;
258 RawIter iter(*this);
259 SkPath::Verb verb;
260 SkPoint pts[4];
261 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkDEBUGCODE(int segmentCount = 0;)
263 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264
265 while ((verb = iter.next(pts)) != kDone_Verb) {
266 int nextPt = -1;
267 switch (verb) {
268 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000269 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 SkDEBUGCODE(++moveCnt);
271 firstPt = prevPt = pts[0];
272 break;
273 case kLine_Verb:
274 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000275 SkASSERT(moveCnt && !closeCount);
276 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 break;
278 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000279 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkASSERT(moveCnt && !closeCount);
281 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282 nextPt = 2;
283 break;
284 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000285 SkASSERT(moveCnt && !closeCount);
286 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 nextPt = 3;
288 break;
289 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 break;
292 default:
293 SkDEBUGFAIL("unknown verb");
294 }
295 if (-1 != nextPt) {
296 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
297 return false;
298 }
299 prevPt = pts[nextPt];
300 }
301 }
302
303 return check_edge_against_rect(prevPt, firstPt, rect, direction);
304}
305
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000306uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000307 uint32_t genID = fPathRef->genID();
308#ifdef SK_BUILD_FOR_ANDROID
309 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
310 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
311#endif
312 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000313}
djsollen@google.come63793a2012-03-21 15:39:03 +0000314
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000315#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000316const SkPath* SkPath::getSourcePath() const {
317 return fSourcePath;
318}
319
320void SkPath::setSourcePath(const SkPath* path) {
321 fSourcePath = path;
322}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000323#endif
324
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325void SkPath::reset() {
326 SkDEBUGCODE(this->validate();)
327
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000328 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000329 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330}
331
332void SkPath::rewind() {
333 SkDEBUGCODE(this->validate();)
334
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000335 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000336 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337}
338
reed@google.com7e6c4d12012-05-10 14:05:43 +0000339bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000340 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000341
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000342 if (2 == verbCount) {
343 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
344 if (kLine_Verb == fPathRef->atVerb(1)) {
345 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000346 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000347 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000348 line[0] = pts[0];
349 line[1] = pts[1];
350 }
351 return true;
352 }
353 }
354 return false;
355}
356
caryclark@google.comf1316942011-07-26 19:54:45 +0000357/*
358 Determines if path is a rect by keeping track of changes in direction
359 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000360
caryclark@google.comf1316942011-07-26 19:54:45 +0000361 The direction is computed such that:
362 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000363 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000364 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000365 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000366
caryclark@google.comf1316942011-07-26 19:54:45 +0000367A rectangle cycles up/right/down/left or up/left/down/right.
368
369The test fails if:
370 The path is closed, and followed by a line.
371 A second move creates a new endpoint.
372 A diagonal line is parsed.
373 There's more than four changes of direction.
374 There's a discontinuity on the line (e.g., a move in the middle)
375 The line reverses direction.
376 The rectangle doesn't complete a cycle.
377 The path contains a quadratic or cubic.
378 The path contains fewer than four points.
379 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000380
caryclark@google.comf1316942011-07-26 19:54:45 +0000381It's OK if the path has:
382 Several colinear line segments composing a rectangle side.
383 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
caryclark@google.comf1316942011-07-26 19:54:45 +0000385The direction takes advantage of the corners found since opposite sides
386must travel in opposite directions.
387
388FIXME: Allow colinear quads and cubics to be treated like lines.
389FIXME: If the API passes fill-only, return true if the filled stroke
390 is a rectangle, though the caller failed to close the path.
391 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
393 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 int corners = 0;
395 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000396 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000397 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000398 first.set(0, 0);
399 last.set(0, 0);
400 int firstDirection = 0;
401 int lastDirection = 0;
402 int nextDirection = 0;
403 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000405 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
407 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000409 savePts = pts;
410 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 autoClose = true;
412 case kLine_Verb: {
413 SkScalar left = last.fX;
414 SkScalar top = last.fY;
415 SkScalar right = pts->fX;
416 SkScalar bottom = pts->fY;
417 ++pts;
418 if (left != right && top != bottom) {
419 return false; // diagonal
420 }
421 if (left == right && top == bottom) {
422 break; // single point on side OK
423 }
424 nextDirection = (left != right) << 0 |
425 (left < right || top < bottom) << 1;
426 if (0 == corners) {
427 firstDirection = nextDirection;
428 first = last;
429 last = pts[-1];
430 corners = 1;
431 closedOrMoved = false;
432 break;
433 }
434 if (closedOrMoved) {
435 return false; // closed followed by a line
436 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000437 if (autoClose && nextDirection == firstDirection) {
438 break; // colinear with first
439 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000440 closedOrMoved = autoClose;
441 if (lastDirection != nextDirection) {
442 if (++corners > 4) {
443 return false; // too many direction changes
444 }
445 }
446 last = pts[-1];
447 if (lastDirection == nextDirection) {
448 break; // colinear segment
449 }
450 // Possible values for corners are 2, 3, and 4.
451 // When corners == 3, nextDirection opposes firstDirection.
452 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000453 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000454 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
455 if ((directionCycle ^ turn) != nextDirection) {
456 return false; // direction didn't follow cycle
457 }
458 break;
459 }
460 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000461 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 case kCubic_Verb:
463 return false; // quadratic, cubic not allowed
464 case kMove_Verb:
465 last = *pts++;
466 closedOrMoved = true;
467 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000468 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000469 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000470 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000472 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 lastDirection = nextDirection;
474 }
475 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000476 bool result = 4 == corners && (first == last || autoClose);
477 if (savePts) {
478 *ptsPtr = savePts;
479 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000480 if (result && isClosed) {
481 *isClosed = autoClose;
482 }
483 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000484 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000485 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000486 return result;
487}
488
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000489bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000490 SkDEBUGCODE(this->validate();)
491 int currVerb = 0;
492 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000493 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
494 if (result && rect) {
495 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000497 return result;
498}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000499
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000500bool SkPath::isRect(bool* isClosed, Direction* direction) const {
501 SkDEBUGCODE(this->validate();)
502 int currVerb = 0;
503 const SkPoint* pts = fPathRef->points();
504 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000505}
506
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000507bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 SkDEBUGCODE(this->validate();)
509 int currVerb = 0;
510 const SkPoint* pts = fPathRef->points();
511 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000512 Direction testDirs[2];
513 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000514 return false;
515 }
516 const SkPoint* last = pts;
517 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000518 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000519 testRects[0].set(first, SkToS32(last - first));
520 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000521 if (testRects[0].contains(testRects[1])) {
522 if (rects) {
523 rects[0] = testRects[0];
524 rects[1] = testRects[1];
525 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000526 if (dirs) {
527 dirs[0] = testDirs[0];
528 dirs[1] = testDirs[1];
529 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000530 return true;
531 }
532 if (testRects[1].contains(testRects[0])) {
533 if (rects) {
534 rects[0] = testRects[1];
535 rects[1] = testRects[0];
536 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000537 if (dirs) {
538 dirs[0] = testDirs[1];
539 dirs[1] = testDirs[0];
540 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000541 return true;
542 }
543 }
544 return false;
545}
546
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000547int SkPath::countPoints() const {
548 return fPathRef->countPoints();
549}
550
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000551int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552 SkDEBUGCODE(this->validate();)
553
554 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000555 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000556 int count = SkMin32(max, fPathRef->countPoints());
557 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
558 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559}
560
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000561SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000562 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
563 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000564 }
565 return SkPoint::Make(0, 0);
566}
567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000568int SkPath::countVerbs() const {
569 return fPathRef->countVerbs();
570}
571
572static inline void copy_verbs_reverse(uint8_t* inorderDst,
573 const uint8_t* reversedSrc,
574 int count) {
575 for (int i = 0; i < count; ++i) {
576 inorderDst[i] = reversedSrc[~i];
577 }
578}
579
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000580int SkPath::getVerbs(uint8_t dst[], int max) const {
581 SkDEBUGCODE(this->validate();)
582
583 SkASSERT(max >= 0);
584 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585 int count = SkMin32(max, fPathRef->countVerbs());
586 copy_verbs_reverse(dst, fPathRef->verbs(), count);
587 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000588}
589
reed@google.com294dd7b2011-10-11 11:58:32 +0000590bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000591 SkDEBUGCODE(this->validate();)
592
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000593 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000594 if (count > 0) {
595 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000598 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000600 if (lastPt) {
601 lastPt->set(0, 0);
602 }
603 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604}
605
606void SkPath::setLastPt(SkScalar x, SkScalar y) {
607 SkDEBUGCODE(this->validate();)
608
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000609 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000610 if (count == 0) {
611 this->moveTo(x, y);
612 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 SkPathRef::Editor ed(&fPathRef);
614 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 }
616}
617
reed@google.com04863fa2011-05-15 04:08:24 +0000618void SkPath::setConvexity(Convexity c) {
619 if (fConvexity != c) {
620 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000621 }
622}
623
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624//////////////////////////////////////////////////////////////////////////////
625// Construction methods
626
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000627#define DIRTY_AFTER_EDIT \
628 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000629 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000630 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000631 } while (0)
632
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633void SkPath::incReserve(U16CPU inc) {
634 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 SkDEBUGCODE(this->validate();)
637}
638
639void SkPath::moveTo(SkScalar x, SkScalar y) {
640 SkDEBUGCODE(this->validate();)
641
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643
reed@google.comd335d1d2012-01-12 18:17:11 +0000644 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000645 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000646
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648}
649
650void SkPath::rMoveTo(SkScalar x, SkScalar y) {
651 SkPoint pt;
652 this->getLastPt(&pt);
653 this->moveTo(pt.fX + x, pt.fY + y);
654}
655
reed@google.comd335d1d2012-01-12 18:17:11 +0000656void SkPath::injectMoveToIfNeeded() {
657 if (fLastMoveToIndex < 0) {
658 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000660 x = y = 0;
661 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000663 x = pt.fX;
664 y = pt.fY;
665 }
666 this->moveTo(x, y);
667 }
668}
669
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670void SkPath::lineTo(SkScalar x, SkScalar y) {
671 SkDEBUGCODE(this->validate();)
672
reed@google.comd335d1d2012-01-12 18:17:11 +0000673 this->injectMoveToIfNeeded();
674
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000675 SkPathRef::Editor ed(&fPathRef);
676 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000677 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678
reed@google.comb54455e2011-05-16 14:16:04 +0000679 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680}
681
682void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000683 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 SkPoint pt;
685 this->getLastPt(&pt);
686 this->lineTo(pt.fX + x, pt.fY + y);
687}
688
689void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
690 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000691
reed@google.comd335d1d2012-01-12 18:17:11 +0000692 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000693
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 SkPathRef::Editor ed(&fPathRef);
695 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 pts[0].set(x1, y1);
697 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000698 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000699
reed@google.comb54455e2011-05-16 14:16:04 +0000700 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701}
702
703void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000704 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 SkPoint pt;
706 this->getLastPt(&pt);
707 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
708}
709
reed@google.com277c3f82013-05-31 15:17:50 +0000710void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
711 SkScalar w) {
712 // check for <= 0 or NaN with this test
713 if (!(w > 0)) {
714 this->lineTo(x2, y2);
715 } else if (!SkScalarIsFinite(w)) {
716 this->lineTo(x1, y1);
717 this->lineTo(x2, y2);
718 } else if (SK_Scalar1 == w) {
719 this->quadTo(x1, y1, x2, y2);
720 } else {
721 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000722
reed@google.com277c3f82013-05-31 15:17:50 +0000723 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000724
reed@google.com277c3f82013-05-31 15:17:50 +0000725 SkPathRef::Editor ed(&fPathRef);
726 SkPoint* pts = ed.growForConic(w);
727 pts[0].set(x1, y1);
728 pts[1].set(x2, y2);
729 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000730
reed@google.com277c3f82013-05-31 15:17:50 +0000731 DIRTY_AFTER_EDIT;
732 }
733}
734
735void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
736 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000737 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000738 SkPoint pt;
739 this->getLastPt(&pt);
740 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
741}
742
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
744 SkScalar x3, SkScalar y3) {
745 SkDEBUGCODE(this->validate();)
746
reed@google.comd335d1d2012-01-12 18:17:11 +0000747 this->injectMoveToIfNeeded();
748
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 SkPathRef::Editor ed(&fPathRef);
750 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751 pts[0].set(x1, y1);
752 pts[1].set(x2, y2);
753 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000754 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755
reed@google.comb54455e2011-05-16 14:16:04 +0000756 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757}
758
759void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
760 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000761 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762 SkPoint pt;
763 this->getLastPt(&pt);
764 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
765 pt.fX + x3, pt.fY + y3);
766}
767
768void SkPath::close() {
769 SkDEBUGCODE(this->validate();)
770
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000771 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000773 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 case kLine_Verb:
775 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000776 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000778 case kMove_Verb: {
779 SkPathRef::Editor ed(&fPathRef);
780 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000782 }
reed@google.com277c3f82013-05-31 15:17:50 +0000783 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000784 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000785 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000786 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000787 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000788 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 }
790 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000791
792 // signal that we need a moveTo to follow us (unless we're done)
793#if 0
794 if (fLastMoveToIndex >= 0) {
795 fLastMoveToIndex = ~fLastMoveToIndex;
796 }
797#else
798 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
799#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800}
801
802///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000803
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000804static void assert_known_direction(int dir) {
805 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
806}
807
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808void SkPath::addRect(const SkRect& rect, Direction dir) {
809 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
810}
811
812void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
813 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000814 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000815 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
816 SkAutoDisableDirectionCheck addc(this);
817
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
819
820 this->incReserve(5);
821
822 this->moveTo(left, top);
823 if (dir == kCCW_Direction) {
824 this->lineTo(left, bottom);
825 this->lineTo(right, bottom);
826 this->lineTo(right, top);
827 } else {
828 this->lineTo(right, top);
829 this->lineTo(right, bottom);
830 this->lineTo(left, bottom);
831 }
832 this->close();
833}
834
reed@google.com744faba2012-05-29 19:54:52 +0000835void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
836 SkDEBUGCODE(this->validate();)
837 if (count <= 0) {
838 return;
839 }
840
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 SkPathRef::Editor ed(&fPathRef);
842 fLastMoveToIndex = ed.pathRef()->countPoints();
843 uint8_t* vb;
844 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000845 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000847
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 memcpy(p, pts, count * sizeof(SkPoint));
849 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000850 if (count > 1) {
851 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
852 // be 0, the compiler will remove the test/branch entirely.
853 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000854 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000855 } else {
856 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000858 }
859 }
860 fSegmentMask |= kLine_SegmentMask;
861 }
862 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000863 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000864 }
865
reed@google.com744faba2012-05-29 19:54:52 +0000866 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000867 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000868}
869
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000870#include "SkGeometry.h"
871
872static int build_arc_points(const SkRect& oval, SkScalar startAngle,
873 SkScalar sweepAngle,
874 SkPoint pts[kSkBuildQuadArcStorage]) {
875
876 if (0 == sweepAngle &&
877 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
878 // Chrome uses this path to move into and out of ovals. If not
879 // treated as a special case the moves can distort the oval's
880 // bounding box (and break the circle special case).
881 pts[0].set(oval.fRight, oval.centerY());
882 return 1;
883 } else if (0 == oval.width() && 0 == oval.height()) {
884 // Chrome will sometimes create 0 radius round rects. Having degenerate
885 // quad segments in the path prevents the path from being recognized as
886 // a rect.
887 // TODO: optimizing the case where only one of width or height is zero
888 // should also be considered. This case, however, doesn't seem to be
889 // as common as the single point case.
890 pts[0].set(oval.fRight, oval.fTop);
891 return 1;
892 }
893
894 SkVector start, stop;
895
896 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
897 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
898 &stop.fX);
899
900 /* If the sweep angle is nearly (but less than) 360, then due to precision
901 loss in radians-conversion and/or sin/cos, we may end up with coincident
902 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
903 of drawing a nearly complete circle (good).
904 e.g. canvas.drawArc(0, 359.99, ...)
905 -vs- canvas.drawArc(0, 359.9, ...)
906 We try to detect this edge case, and tweak the stop vector
907 */
908 if (start == stop) {
909 SkScalar sw = SkScalarAbs(sweepAngle);
910 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
911 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
912 // make a guess at a tiny angle (in radians) to tweak by
913 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
914 // not sure how much will be enough, so we use a loop
915 do {
916 stopRad -= deltaRad;
917 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
918 } while (start == stop);
919 }
920 }
921
922 SkMatrix matrix;
923
924 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
925 matrix.postTranslate(oval.centerX(), oval.centerY());
926
927 return SkBuildQuadArc(start, stop,
928 sweepAngle > 0 ? kCW_SkRotationDirection :
929 kCCW_SkRotationDirection,
930 &matrix, pts);
931}
932
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000933void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000935 SkRRect rrect;
936 rrect.setRectRadii(rect, (const SkVector*) radii);
937 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938}
939
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000940/* The inline clockwise and counterclockwise round rect quad approximations
941 make it easier to see the symmetry patterns used by add corner quads.
942Clockwise corner value
943 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
944 path->quadTo(rect.fLeft, rect.fTop + offPtY,
945 rect.fLeft + midPtX, rect.fTop + midPtY);
946 path->quadTo(rect.fLeft + offPtX, rect.fTop,
947 rect.fLeft + rx, rect.fTop);
948
949 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
950 path->quadTo(rect.fRight - offPtX, rect.fTop,
951 rect.fRight - midPtX, rect.fTop + midPtY);
952 path->quadTo(rect.fRight, rect.fTop + offPtY,
953 rect.fRight, rect.fTop + ry);
954
955 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
956 path->quadTo(rect.fRight, rect.fBottom - offPtY,
957 rect.fRight - midPtX, rect.fBottom - midPtY);
958 path->quadTo(rect.fRight - offPtX, rect.fBottom,
959 rect.fRight - rx, rect.fBottom);
960
961 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
962 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
963 rect.fLeft + midPtX, rect.fBottom - midPtY);
964 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
965 rect.fLeft, rect.fBottom - ry);
966
967Counterclockwise
968 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
969 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
970 rect.fLeft + midPtX, rect.fBottom - midPtY);
971 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
972 rect.fLeft + rx, rect.fBottom);
973
974 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
975 path->quadTo(rect.fRight - offPtX, rect.fBottom,
976 rect.fRight - midPtX, rect.fBottom - midPtY);
977 path->quadTo(rect.fRight, rect.fBottom - offPtY,
978 rect.fRight, rect.fBottom - ry);
979
980 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
981 path->quadTo(rect.fRight, rect.fTop + offPtY,
982 rect.fRight - midPtX, rect.fTop + midPtY);
983 path->quadTo(rect.fRight - offPtX, rect.fTop,
984 rect.fRight - rx, rect.fTop);
985
986 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
987 path->quadTo(rect.fLeft + offPtX, rect.fTop,
988 rect.fLeft + midPtX, rect.fTop + midPtY);
989 path->quadTo(rect.fLeft, rect.fTop + offPtY,
990 rect.fLeft, rect.fTop + ry);
991*/
992static void add_corner_quads(SkPath* path, const SkRRect& rrect,
993 SkRRect::Corner corner, SkPath::Direction dir) {
994 const SkRect& rect = rrect.rect();
995 const SkVector& radii = rrect.radii(corner);
996 SkScalar rx = radii.fX;
997 SkScalar ry = radii.fY;
998 // The mid point of the quadratic arc approximation is half way between the two
999 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001000 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1001 SkScalar midPtX = rx * mid;
1002 SkScalar midPtY = ry * mid;
1003 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1004 SkScalar offPtX = rx * control;
1005 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001006 static const int kCornerPts = 5;
1007 SkScalar xOff[kCornerPts];
1008 SkScalar yOff[kCornerPts];
1009
1010 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1011 SkASSERT(dir == SkPath::kCCW_Direction
1012 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1013 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1014 xOff[0] = xOff[1] = 0;
1015 xOff[2] = midPtX;
1016 xOff[3] = offPtX;
1017 xOff[4] = rx;
1018 yOff[0] = ry;
1019 yOff[1] = offPtY;
1020 yOff[2] = midPtY;
1021 yOff[3] = yOff[4] = 0;
1022 } else {
1023 xOff[0] = rx;
1024 xOff[1] = offPtX;
1025 xOff[2] = midPtX;
1026 xOff[3] = xOff[4] = 0;
1027 yOff[0] = yOff[1] = 0;
1028 yOff[2] = midPtY;
1029 yOff[3] = offPtY;
1030 yOff[4] = ry;
1031 }
1032 if ((corner - 1) & 2) {
1033 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1034 for (int i = 0; i < kCornerPts; ++i) {
1035 xOff[i] = rect.fLeft + xOff[i];
1036 }
1037 } else {
1038 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1039 for (int i = 0; i < kCornerPts; ++i) {
1040 xOff[i] = rect.fRight - xOff[i];
1041 }
1042 }
1043 if (corner < SkRRect::kLowerRight_Corner) {
1044 for (int i = 0; i < kCornerPts; ++i) {
1045 yOff[i] = rect.fTop + yOff[i];
1046 }
1047 } else {
1048 for (int i = 0; i < kCornerPts; ++i) {
1049 yOff[i] = rect.fBottom - yOff[i];
1050 }
1051 }
1052
1053 SkPoint lastPt;
1054 SkAssertResult(path->getLastPt(&lastPt));
1055 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1056 path->lineTo(xOff[0], yOff[0]);
1057 }
1058 if (rx || ry) {
1059 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1060 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1061 } else {
1062 path->lineTo(xOff[2], yOff[2]);
1063 path->lineTo(xOff[4], yOff[4]);
1064 }
1065}
1066
reed@google.com4ed0fb72012-12-12 20:48:18 +00001067void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001068 assert_known_direction(dir);
1069
1070 if (rrect.isEmpty()) {
1071 return;
1072 }
1073
reed@google.com4ed0fb72012-12-12 20:48:18 +00001074 const SkRect& bounds = rrect.getBounds();
1075
1076 if (rrect.isRect()) {
1077 this->addRect(bounds, dir);
1078 } else if (rrect.isOval()) {
1079 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001080#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001081 } else if (rrect.isSimple()) {
1082 const SkVector& rad = rrect.getSimpleRadii();
1083 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001084#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001085 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001086 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001087
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001088 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001089 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001090
1091 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001092 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001093 this->moveTo(bounds.fLeft,
1094 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1095 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1096 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1097 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1098 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001100 this->moveTo(bounds.fLeft,
1101 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1102 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1103 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1104 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1105 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001106 }
1107 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001108 }
1109}
1110
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001111bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001112 int count = fPathRef->countVerbs();
1113 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1114 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001115 if (*verbs == kLine_Verb ||
1116 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001117 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001118 *verbs == kCubic_Verb) {
1119 return false;
1120 }
1121 ++verbs;
1122 }
1123 return true;
1124}
1125
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001126#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001127#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001128#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001129
1130void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1131 Direction dir) {
1132 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001133
humper@google.com75e3ca12013-04-08 21:44:11 +00001134 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001135 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001136 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001137 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001138 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1139 return;
1140 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001141
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001142#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001143 SkScalar w = rect.width();
1144 SkScalar halfW = SkScalarHalf(w);
1145 SkScalar h = rect.height();
1146 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001147
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001148 if (halfW <= 0 || halfH <= 0) {
1149 return;
1150 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001151
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001152 bool skip_hori = rx >= halfW;
1153 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001154
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001155 if (skip_hori && skip_vert) {
1156 this->addOval(rect, dir);
1157 return;
1158 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001159
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001161
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001162 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001163 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001164
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001165 if (skip_hori) {
1166 rx = halfW;
1167 } else if (skip_vert) {
1168 ry = halfH;
1169 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001170 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1171 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001172
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001173 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001174 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001175 if (dir == kCCW_Direction) {
1176 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001177 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001178 }
1179 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1180 rect.fLeft, rect.fTop + ry - sy,
1181 rect.fLeft, rect.fTop + ry); // top-left
1182 if (!skip_vert) {
1183 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1184 }
1185 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1186 rect.fLeft + rx - sx, rect.fBottom,
1187 rect.fLeft + rx, rect.fBottom); // bot-left
1188 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001189 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001190 }
1191 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1192 rect.fRight, rect.fBottom - ry + sy,
1193 rect.fRight, rect.fBottom - ry); // bot-right
1194 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001195 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001196 }
1197 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1198 rect.fRight - rx + sx, rect.fTop,
1199 rect.fRight - rx, rect.fTop); // top-right
1200 } else {
1201 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1202 rect.fRight, rect.fTop + ry - sy,
1203 rect.fRight, rect.fTop + ry); // top-right
1204 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001205 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001206 }
1207 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1208 rect.fRight - rx + sx, rect.fBottom,
1209 rect.fRight - rx, rect.fBottom); // bot-right
1210 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001211 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001212 }
1213 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1214 rect.fLeft, rect.fBottom - ry + sy,
1215 rect.fLeft, rect.fBottom - ry); // bot-left
1216 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001217 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001218 }
1219 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1220 rect.fLeft + rx - sx, rect.fTop,
1221 rect.fLeft + rx, rect.fTop); // top-left
1222 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001223 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001224 }
1225 }
1226 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001227#else
1228 SkRRect rrect;
1229 rrect.setRectXY(rect, rx, ry);
1230 this->addRRect(rrect, dir);
1231#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001232}
1233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001235 assert_known_direction(dir);
1236
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001237 /* If addOval() is called after previous moveTo(),
1238 this path is still marked as an oval. This is used to
1239 fit into WebKit's calling sequences.
1240 We can't simply check isEmpty() in this case, as additional
1241 moveTo() would mark the path non empty.
1242 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001243 bool isOval = hasOnlyMoveTos();
1244 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001245 fDirection = dir;
1246 } else {
1247 fDirection = kUnknown_Direction;
1248 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001249
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001250 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001251
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 SkAutoPathBoundsUpdate apbu(this, oval);
1253
1254 SkScalar cx = oval.centerX();
1255 SkScalar cy = oval.centerY();
1256 SkScalar rx = SkScalarHalf(oval.width());
1257 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1260 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1261 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1262 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1263
1264 /*
1265 To handle imprecision in computing the center and radii, we revert to
1266 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1267 to ensure that we don't exceed the oval's bounds *ever*, since we want
1268 to use oval for our fast-bounds, rather than have to recompute it.
1269 */
1270 const SkScalar L = oval.fLeft; // cx - rx
1271 const SkScalar T = oval.fTop; // cy - ry
1272 const SkScalar R = oval.fRight; // cx + rx
1273 const SkScalar B = oval.fBottom; // cy + ry
1274
1275 this->incReserve(17); // 8 quads + close
1276 this->moveTo(R, cy);
1277 if (dir == kCCW_Direction) {
1278 this->quadTo( R, cy - sy, cx + mx, cy - my);
1279 this->quadTo(cx + sx, T, cx , T);
1280 this->quadTo(cx - sx, T, cx - mx, cy - my);
1281 this->quadTo( L, cy - sy, L, cy );
1282 this->quadTo( L, cy + sy, cx - mx, cy + my);
1283 this->quadTo(cx - sx, B, cx , B);
1284 this->quadTo(cx + sx, B, cx + mx, cy + my);
1285 this->quadTo( R, cy + sy, R, cy );
1286 } else {
1287 this->quadTo( R, cy + sy, cx + mx, cy + my);
1288 this->quadTo(cx + sx, B, cx , B);
1289 this->quadTo(cx - sx, B, cx - mx, cy + my);
1290 this->quadTo( L, cy + sy, L, cy );
1291 this->quadTo( L, cy - sy, cx - mx, cy - my);
1292 this->quadTo(cx - sx, T, cx , T);
1293 this->quadTo(cx + sx, T, cx + mx, cy - my);
1294 this->quadTo( R, cy - sy, R, cy );
1295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297
robertphillips@google.com466310d2013-12-03 16:43:54 +00001298 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001299
robertphillips@google.com466310d2013-12-03 16:43:54 +00001300 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001301}
1302
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1304 if (r > 0) {
1305 SkRect rect;
1306 rect.set(x - r, y - r, x + r, y + r);
1307 this->addOval(rect, dir);
1308 }
1309}
1310
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1312 bool forceMoveTo) {
1313 if (oval.width() < 0 || oval.height() < 0) {
1314 return;
1315 }
1316
1317 SkPoint pts[kSkBuildQuadArcStorage];
1318 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1319 SkASSERT((count & 1) == 1);
1320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001321 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 forceMoveTo = true;
1323 }
1324 this->incReserve(count);
1325 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1326 for (int i = 1; i < count; i += 2) {
1327 this->quadTo(pts[i], pts[i+1]);
1328 }
1329}
1330
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001331void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 if (oval.isEmpty() || 0 == sweepAngle) {
1333 return;
1334 }
1335
1336 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1337
1338 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1339 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1340 return;
1341 }
1342
1343 SkPoint pts[kSkBuildQuadArcStorage];
1344 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1345
1346 this->incReserve(count);
1347 this->moveTo(pts[0]);
1348 for (int i = 1; i < count; i += 2) {
1349 this->quadTo(pts[i], pts[i+1]);
1350 }
1351}
1352
1353/*
1354 Need to handle the case when the angle is sharp, and our computed end-points
1355 for the arc go behind pt1 and/or p2...
1356*/
1357void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1358 SkScalar radius) {
1359 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001360
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 // need to know our prev pt so we can construct tangent vectors
1362 {
1363 SkPoint start;
1364 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001365 // Handle degenerate cases by adding a line to the first point and
1366 // bailing out.
1367 if ((x1 == start.fX && y1 == start.fY) ||
1368 (x1 == x2 && y1 == y2) ||
1369 radius == 0) {
1370 this->lineTo(x1, y1);
1371 return;
1372 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 before.setNormalize(x1 - start.fX, y1 - start.fY);
1374 after.setNormalize(x2 - x1, y2 - y1);
1375 }
reed@google.comabf15c12011-01-18 20:35:51 +00001376
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 SkScalar cosh = SkPoint::DotProduct(before, after);
1378 SkScalar sinh = SkPoint::CrossProduct(before, after);
1379
1380 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001381 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 return;
1383 }
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1386 if (dist < 0) {
1387 dist = -dist;
1388 }
1389
1390 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1391 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1392 SkRotationDirection arcDir;
1393
1394 // now turn before/after into normals
1395 if (sinh > 0) {
1396 before.rotateCCW();
1397 after.rotateCCW();
1398 arcDir = kCW_SkRotationDirection;
1399 } else {
1400 before.rotateCW();
1401 after.rotateCW();
1402 arcDir = kCCW_SkRotationDirection;
1403 }
1404
1405 SkMatrix matrix;
1406 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001407
reed@android.com8a1c16f2008-12-17 15:59:43 +00001408 matrix.setScale(radius, radius);
1409 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1410 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001411
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 this->incReserve(count);
1415 // [xx,yy] == pts[0]
1416 this->lineTo(xx, yy);
1417 for (int i = 1; i < count; i += 2) {
1418 this->quadTo(pts[i], pts[i+1]);
1419 }
1420}
1421
1422///////////////////////////////////////////////////////////////////////////////
1423
1424void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1425 SkMatrix matrix;
1426
1427 matrix.setTranslate(dx, dy);
1428 this->addPath(path, matrix);
1429}
1430
1431void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001432 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001434 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435 SkPoint pts[4];
1436 Verb verb;
1437
1438 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1439
1440 while ((verb = iter.next(pts)) != kDone_Verb) {
1441 switch (verb) {
1442 case kMove_Verb:
1443 proc(matrix, &pts[0], &pts[0], 1);
1444 this->moveTo(pts[0]);
1445 break;
1446 case kLine_Verb:
1447 proc(matrix, &pts[1], &pts[1], 1);
1448 this->lineTo(pts[1]);
1449 break;
1450 case kQuad_Verb:
1451 proc(matrix, &pts[1], &pts[1], 2);
1452 this->quadTo(pts[1], pts[2]);
1453 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001454 case kConic_Verb:
1455 proc(matrix, &pts[1], &pts[1], 2);
1456 this->conicTo(pts[1], pts[2], iter.conicWeight());
1457 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 case kCubic_Verb:
1459 proc(matrix, &pts[1], &pts[1], 3);
1460 this->cubicTo(pts[1], pts[2], pts[3]);
1461 break;
1462 case kClose_Verb:
1463 this->close();
1464 break;
1465 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001466 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 }
1468 }
1469}
1470
1471///////////////////////////////////////////////////////////////////////////////
1472
reed@google.com277c3f82013-05-31 15:17:50 +00001473static int pts_in_verb(unsigned verb) {
1474 static const uint8_t gPtsInVerb[] = {
1475 1, // kMove
1476 1, // kLine
1477 2, // kQuad
1478 2, // kConic
1479 3, // kCubic
1480 0, // kClose
1481 0 // kDone
1482 };
1483
1484 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1485 return gPtsInVerb[verb];
1486}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488// ignore the last point of the 1st contour
1489void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001490 int i, vcount = path.fPathRef->countVerbs();
1491 // exit early if the path is empty, or just has a moveTo.
1492 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 return;
1494 }
1495
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001496 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 const uint8_t* verbs = path.fPathRef->verbs();
1499 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001500 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001502 SkASSERT(verbs[~0] == kMove_Verb);
1503 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001504 unsigned v = verbs[~i];
1505 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506 if (n == 0) {
1507 break;
1508 }
1509 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001510 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 }
1512
1513 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 case kLine_Verb:
1516 this->lineTo(pts[-1].fX, pts[-1].fY);
1517 break;
1518 case kQuad_Verb:
1519 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1520 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001521 case kConic_Verb:
1522 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1523 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 case kCubic_Verb:
1525 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1526 pts[-3].fX, pts[-3].fY);
1527 break;
1528 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001529 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 break;
1531 }
reed@google.com277c3f82013-05-31 15:17:50 +00001532 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 }
1534}
1535
reed@google.com63d73742012-01-10 15:33:12 +00001536void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001538
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 const SkPoint* pts = src.fPathRef->pointsEnd();
1540 // we will iterator through src's verbs backwards
1541 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1542 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001543 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001544
1545 bool needMove = true;
1546 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 while (verbs < verbsEnd) {
1548 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001549 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001550
1551 if (needMove) {
1552 --pts;
1553 this->moveTo(pts->fX, pts->fY);
1554 needMove = false;
1555 }
1556 pts -= n;
1557 switch (v) {
1558 case kMove_Verb:
1559 if (needClose) {
1560 this->close();
1561 needClose = false;
1562 }
1563 needMove = true;
1564 pts += 1; // so we see the point in "if (needMove)" above
1565 break;
1566 case kLine_Verb:
1567 this->lineTo(pts[0]);
1568 break;
1569 case kQuad_Verb:
1570 this->quadTo(pts[1], pts[0]);
1571 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001572 case kConic_Verb:
1573 this->conicTo(pts[1], pts[0], *--conicWeights);
1574 break;
reed@google.com63d73742012-01-10 15:33:12 +00001575 case kCubic_Verb:
1576 this->cubicTo(pts[2], pts[1], pts[0]);
1577 break;
1578 case kClose_Verb:
1579 needClose = true;
1580 break;
1581 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001582 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001583 }
1584 }
1585}
1586
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587///////////////////////////////////////////////////////////////////////////////
1588
1589void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1590 SkMatrix matrix;
1591
1592 matrix.setTranslate(dx, dy);
1593 this->transform(matrix, dst);
1594}
1595
1596#include "SkGeometry.h"
1597
1598static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1599 int level = 2) {
1600 if (--level >= 0) {
1601 SkPoint tmp[5];
1602
1603 SkChopQuadAtHalf(pts, tmp);
1604 subdivide_quad_to(path, &tmp[0], level);
1605 subdivide_quad_to(path, &tmp[2], level);
1606 } else {
1607 path->quadTo(pts[1], pts[2]);
1608 }
1609}
1610
1611static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1612 int level = 2) {
1613 if (--level >= 0) {
1614 SkPoint tmp[7];
1615
1616 SkChopCubicAtHalf(pts, tmp);
1617 subdivide_cubic_to(path, &tmp[0], level);
1618 subdivide_cubic_to(path, &tmp[3], level);
1619 } else {
1620 path->cubicTo(pts[1], pts[2], pts[3]);
1621 }
1622}
1623
1624void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1625 SkDEBUGCODE(this->validate();)
1626 if (dst == NULL) {
1627 dst = (SkPath*)this;
1628 }
1629
tomhudson@google.com8d430182011-06-06 19:11:19 +00001630 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 SkPath tmp;
1632 tmp.fFillType = fFillType;
1633
1634 SkPath::Iter iter(*this, false);
1635 SkPoint pts[4];
1636 SkPath::Verb verb;
1637
reed@google.com4a3b7142012-05-16 17:16:46 +00001638 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 switch (verb) {
1640 case kMove_Verb:
1641 tmp.moveTo(pts[0]);
1642 break;
1643 case kLine_Verb:
1644 tmp.lineTo(pts[1]);
1645 break;
1646 case kQuad_Verb:
1647 subdivide_quad_to(&tmp, pts);
1648 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001649 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001650 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001651 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1652 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 case kCubic_Verb:
1654 subdivide_cubic_to(&tmp, pts);
1655 break;
1656 case kClose_Verb:
1657 tmp.close();
1658 break;
1659 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001660 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 break;
1662 }
1663 }
1664
1665 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001666 SkPathRef::Editor ed(&dst->fPathRef);
1667 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001668 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001669 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001670 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1671
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001674 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001675 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001677
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001678 if (kUnknown_Direction == fDirection) {
1679 dst->fDirection = kUnknown_Direction;
1680 } else {
1681 SkScalar det2x2 =
1682 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1683 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1684 if (det2x2 < 0) {
1685 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1686 } else if (det2x2 > 0) {
1687 dst->fDirection = fDirection;
1688 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001689 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001690 dst->fDirection = kUnknown_Direction;
1691 }
1692 }
1693
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 SkDEBUGCODE(dst->validate();)
1695 }
1696}
1697
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698///////////////////////////////////////////////////////////////////////////////
1699///////////////////////////////////////////////////////////////////////////////
1700
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001702 kEmptyContour_SegmentState, // The current contour is empty. We may be
1703 // starting processing or we may have just
1704 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001705 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1706 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1707 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708};
1709
1710SkPath::Iter::Iter() {
1711#ifdef SK_DEBUG
1712 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001713 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001715 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001716 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717#endif
1718 // need to init enough to make next() harmlessly return kDone_Verb
1719 fVerbs = NULL;
1720 fVerbStop = NULL;
1721 fNeedClose = false;
1722}
1723
1724SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1725 this->setPath(path, forceClose);
1726}
1727
1728void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001729 fPts = path.fPathRef->points();
1730 fVerbs = path.fPathRef->verbs();
1731 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001732 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001733 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001734 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735 fForceClose = SkToU8(forceClose);
1736 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001737 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738}
1739
1740bool SkPath::Iter::isClosedContour() const {
1741 if (fVerbs == NULL || fVerbs == fVerbStop) {
1742 return false;
1743 }
1744 if (fForceClose) {
1745 return true;
1746 }
1747
1748 const uint8_t* verbs = fVerbs;
1749 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001751 if (kMove_Verb == *(verbs - 1)) {
1752 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 }
1754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001755 while (verbs > stop) {
1756 // verbs points one beyond the current verb, decrement first.
1757 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 if (kMove_Verb == v) {
1759 break;
1760 }
1761 if (kClose_Verb == v) {
1762 return true;
1763 }
1764 }
1765 return false;
1766}
1767
1768SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001769 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001771 // A special case: if both points are NaN, SkPoint::operation== returns
1772 // false, but the iterator expects that they are treated as the same.
1773 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001774 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1775 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001776 return kClose_Verb;
1777 }
1778
reed@google.com9e25dbf2012-05-15 17:05:38 +00001779 pts[0] = fLastPt;
1780 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 fLastPt = fMoveTo;
1782 fCloseLine = true;
1783 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001784 } else {
1785 pts[0] = fMoveTo;
1786 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788}
1789
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001791 if (fSegmentState == kAfterMove_SegmentState) {
1792 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001794 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1797 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001798 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800}
1801
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802void SkPath::Iter::consumeDegenerateSegments() {
1803 // We need to step over anything that will not move the current draw point
1804 // forward before the next move is seen
1805 const uint8_t* lastMoveVerb = 0;
1806 const SkPoint* lastMovePt = 0;
1807 SkPoint lastPt = fLastPt;
1808 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001809 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 switch (verb) {
1811 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812 // Keep a record of this most recent move
1813 lastMoveVerb = fVerbs;
1814 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001815 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001816 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 fPts++;
1818 break;
1819
1820 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001821 // A close when we are in a segment is always valid except when it
1822 // follows a move which follows a segment.
1823 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824 return;
1825 }
1826 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 break;
1829
1830 case kLine_Verb:
1831 if (!IsLineDegenerate(lastPt, fPts[0])) {
1832 if (lastMoveVerb) {
1833 fVerbs = lastMoveVerb;
1834 fPts = lastMovePt;
1835 return;
1836 }
1837 return;
1838 }
1839 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001840 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001841 fPts++;
1842 break;
1843
reed@google.com277c3f82013-05-31 15:17:50 +00001844 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 case kQuad_Verb:
1846 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1847 if (lastMoveVerb) {
1848 fVerbs = lastMoveVerb;
1849 fPts = lastMovePt;
1850 return;
1851 }
1852 return;
1853 }
1854 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001857 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 break;
1859
1860 case kCubic_Verb:
1861 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1862 if (lastMoveVerb) {
1863 fVerbs = lastMoveVerb;
1864 fPts = lastMovePt;
1865 return;
1866 }
1867 return;
1868 }
1869 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 fPts += 3;
1872 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001873
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001875 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 }
1877 }
1878}
1879
reed@google.com4a3b7142012-05-16 17:16:46 +00001880SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001881 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 // Close the curve if requested and if there is some curve to close
1885 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001886 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 return kLine_Verb;
1888 }
1889 fNeedClose = false;
1890 return kClose_Verb;
1891 }
1892 return kDone_Verb;
1893 }
1894
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001895 // fVerbs is one beyond the current verb, decrement first
1896 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897 const SkPoint* SK_RESTRICT srcPts = fPts;
1898 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899
1900 switch (verb) {
1901 case kMove_Verb:
1902 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001903 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 verb = this->autoClose(pts);
1905 if (verb == kClose_Verb) {
1906 fNeedClose = false;
1907 }
1908 return (Verb)verb;
1909 }
1910 if (fVerbs == fVerbStop) { // might be a trailing moveto
1911 return kDone_Verb;
1912 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001913 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001914 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001916 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 fNeedClose = fForceClose;
1919 break;
1920 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001921 pts[0] = this->cons_moveTo();
1922 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 fLastPt = srcPts[0];
1924 fCloseLine = false;
1925 srcPts += 1;
1926 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001927 case kConic_Verb:
1928 fConicWeights += 1;
1929 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 pts[0] = this->cons_moveTo();
1932 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 fLastPt = srcPts[1];
1934 srcPts += 2;
1935 break;
1936 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001937 pts[0] = this->cons_moveTo();
1938 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fLastPt = srcPts[2];
1940 srcPts += 3;
1941 break;
1942 case kClose_Verb:
1943 verb = this->autoClose(pts);
1944 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001945 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 } else {
1947 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001948 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 break;
1952 }
1953 fPts = srcPts;
1954 return (Verb)verb;
1955}
1956
1957///////////////////////////////////////////////////////////////////////////////
1958
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001959SkPath::RawIter::RawIter() {
1960#ifdef SK_DEBUG
1961 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001962 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001963 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1964#endif
1965 // need to init enough to make next() harmlessly return kDone_Verb
1966 fVerbs = NULL;
1967 fVerbStop = NULL;
1968}
1969
1970SkPath::RawIter::RawIter(const SkPath& path) {
1971 this->setPath(path);
1972}
1973
1974void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 fPts = path.fPathRef->points();
1976 fVerbs = path.fPathRef->verbs();
1977 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001978 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001979 fMoveTo.fX = fMoveTo.fY = 0;
1980 fLastPt.fX = fLastPt.fY = 0;
1981}
1982
1983SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001984 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001985 if (fVerbs == fVerbStop) {
1986 return kDone_Verb;
1987 }
1988
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001989 // fVerbs points one beyond next verb so decrement first.
1990 unsigned verb = *(--fVerbs);
1991 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992
1993 switch (verb) {
1994 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001995 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996 fMoveTo = srcPts[0];
1997 fLastPt = fMoveTo;
1998 srcPts += 1;
1999 break;
2000 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002001 pts[0] = fLastPt;
2002 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002003 fLastPt = srcPts[0];
2004 srcPts += 1;
2005 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002006 case kConic_Verb:
2007 fConicWeights += 1;
2008 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002009 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002010 pts[0] = fLastPt;
2011 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012 fLastPt = srcPts[1];
2013 srcPts += 2;
2014 break;
2015 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002016 pts[0] = fLastPt;
2017 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002018 fLastPt = srcPts[2];
2019 srcPts += 3;
2020 break;
2021 case kClose_Verb:
2022 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002023 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002024 break;
2025 }
2026 fPts = srcPts;
2027 return (Verb)verb;
2028}
2029
2030///////////////////////////////////////////////////////////////////////////////
2031
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002033 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034*/
2035
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002036size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 SkDEBUGCODE(this->validate();)
2038
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002039 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002040 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002041 return SkAlign4(byteCount);
2042 }
2043
2044 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002045
robertphillips@google.com466310d2013-12-03 16:43:54 +00002046 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002047 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002048 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002049 (fDirection << kDirection_SerializationShift)
robertphillips@google.com466310d2013-12-03 16:43:54 +00002050#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
robertphillips@google.com11e05552013-12-03 19:46:58 +00002051 | (0x1 << kNewFormat_SerializationShift)
robertphillips@google.com466310d2013-12-03 16:43:54 +00002052#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002053 ;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002054
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002055 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002056
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002057 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002059 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002060 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061}
2062
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002063size_t SkPath::readFromMemory(const void* storage, size_t length) {
2064 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002065
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002066 int32_t packed;
2067 if (!buffer.readS32(&packed)) {
2068 return 0;
2069 }
2070
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002071 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2072 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002073 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002074 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com466310d2013-12-03 16:43:54 +00002075#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
robertphillips@google.com11e05552013-12-03 19:46:58 +00002076 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002077#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002078
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002079 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer
robertphillips@google.com466310d2013-12-03 16:43:54 +00002080#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V16_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002081 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002082#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002083 );
reed@google.comabf15c12011-01-18 20:35:51 +00002084
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002085 size_t sizeRead = 0;
2086 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002087 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002088 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002089 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002090 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002091 } else if (NULL != pathRef) {
2092 // If the buffer is not valid, pathRef should be NULL
2093 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002094 }
2095 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096}
2097
2098///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099
reed@google.com51bbe752013-01-17 22:07:50 +00002100#include "SkString.h"
2101
2102static void append_scalar(SkString* str, SkScalar value) {
2103 SkString tmp;
2104 tmp.printf("%g", value);
2105 if (tmp.contains('.')) {
2106 tmp.appendUnichar('f');
2107 }
2108 str->append(tmp);
2109}
2110
2111static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002112 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002113 str->append(label);
2114 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002115
reed@google.com51bbe752013-01-17 22:07:50 +00002116 const SkScalar* values = &pts[0].fX;
2117 count *= 2;
2118
2119 for (int i = 0; i < count; ++i) {
2120 append_scalar(str, values[i]);
2121 if (i < count - 1) {
2122 str->append(", ");
2123 }
2124 }
reed@google.com277c3f82013-05-31 15:17:50 +00002125 if (conicWeight >= 0) {
2126 str->append(", ");
2127 append_scalar(str, conicWeight);
2128 }
reed@google.com51bbe752013-01-17 22:07:50 +00002129 str->append(");\n");
2130}
2131
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132void SkPath::dump(bool forceClose, const char title[]) const {
2133 Iter iter(*this, forceClose);
2134 SkPoint pts[4];
2135 Verb verb;
2136
2137 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2138 title ? title : "");
2139
reed@google.com51bbe752013-01-17 22:07:50 +00002140 SkString builder;
2141
reed@google.com4a3b7142012-05-16 17:16:46 +00002142 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 switch (verb) {
2144 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002145 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 break;
2147 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002148 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 break;
2150 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002151 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002152 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002153 case kConic_Verb:
2154 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2155 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002157 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 break;
2159 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002160 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 break;
2162 default:
2163 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2164 verb = kDone_Verb; // stop the loop
2165 break;
2166 }
2167 }
reed@google.com51bbe752013-01-17 22:07:50 +00002168 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169}
2170
reed@android.come522ca52009-11-23 20:10:41 +00002171void SkPath::dump() const {
2172 this->dump(false);
2173}
2174
2175#ifdef SK_DEBUG
2176void SkPath::validate() const {
2177 SkASSERT(this != NULL);
2178 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002179
djsollen@google.com077348c2012-10-22 20:23:32 +00002180#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002181 if (!fBoundsIsDirty) {
2182 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002183
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002184 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002185 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002186
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002187 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002188 // if we're empty, fBounds may be empty but translated, so we can't
2189 // necessarily compare to bounds directly
2190 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2191 // be [2, 2, 2, 2]
2192 SkASSERT(bounds.isEmpty());
2193 SkASSERT(fBounds.isEmpty());
2194 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002195 if (bounds.isEmpty()) {
2196 SkASSERT(fBounds.isEmpty());
2197 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002198 if (!fBounds.isEmpty()) {
2199 SkASSERT(fBounds.contains(bounds));
2200 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002201 }
reed@android.come522ca52009-11-23 20:10:41 +00002202 }
2203 }
reed@google.com10296cc2011-09-21 12:29:05 +00002204
2205 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002206 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2207 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2208 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002209 case kLine_Verb:
2210 mask |= kLine_SegmentMask;
2211 break;
2212 case kQuad_Verb:
2213 mask |= kQuad_SegmentMask;
2214 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002215 case kConic_Verb:
2216 mask |= kConic_SegmentMask;
2217 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002218 case kCubic_Verb:
2219 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002220 case kMove_Verb: // these verbs aren't included in the segment mask.
2221 case kClose_Verb:
2222 break;
2223 case kDone_Verb:
2224 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2225 break;
2226 default:
2227 SkDEBUGFAIL("Unknown Verb");
2228 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002229 }
2230 }
2231 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002232#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002233}
djsollen@google.com077348c2012-10-22 20:23:32 +00002234#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002235
reed@google.com04863fa2011-05-15 04:08:24 +00002236///////////////////////////////////////////////////////////////////////////////
2237
reed@google.com0b7b9822011-05-16 12:29:27 +00002238static int sign(SkScalar x) { return x < 0; }
2239#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002240
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002241static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2242 // The error epsilon was empirically derived; worse case round rects
2243 // with a mid point outset by 2x float epsilon in tests had an error
2244 // of 12.
2245 const int epsilon = 16;
2246 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2247 return false;
2248 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002249 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002250 int aBits = SkFloatAs2sCompliment(compA);
2251 int bBits = SkFloatAs2sCompliment(compB);
2252 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002253}
2254
2255// only valid for a single contour
2256struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002257 Convexicator()
2258 : fPtCount(0)
2259 , fConvexity(SkPath::kConvex_Convexity)
2260 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002261 fSign = 0;
2262 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002263 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002264 fCurrPt.set(0, 0);
2265 fVec0.set(0, 0);
2266 fVec1.set(0, 0);
2267 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002268
2269 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002270 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002271 }
2272
2273 SkPath::Convexity getConvexity() const { return fConvexity; }
2274
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275 /** The direction returned is only valid if the path is determined convex */
2276 SkPath::Direction getDirection() const { return fDirection; }
2277
reed@google.com04863fa2011-05-15 04:08:24 +00002278 void addPt(const SkPoint& pt) {
2279 if (SkPath::kConcave_Convexity == fConvexity) {
2280 return;
2281 }
2282
2283 if (0 == fPtCount) {
2284 fCurrPt = pt;
2285 ++fPtCount;
2286 } else {
2287 SkVector vec = pt - fCurrPt;
2288 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002289 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002290 fCurrPt = pt;
2291 if (++fPtCount == 2) {
2292 fFirstVec = fVec1 = vec;
2293 } else {
2294 SkASSERT(fPtCount > 2);
2295 this->addVec(vec);
2296 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002297
reed@google.com85b6e392011-05-15 20:25:17 +00002298 int sx = sign(vec.fX);
2299 int sy = sign(vec.fY);
2300 fDx += (sx != fSx);
2301 fDy += (sy != fSy);
2302 fSx = sx;
2303 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002304
reed@google.com85b6e392011-05-15 20:25:17 +00002305 if (fDx > 3 || fDy > 3) {
2306 fConvexity = SkPath::kConcave_Convexity;
2307 }
reed@google.com04863fa2011-05-15 04:08:24 +00002308 }
2309 }
2310 }
2311
2312 void close() {
2313 if (fPtCount > 2) {
2314 this->addVec(fFirstVec);
2315 }
2316 }
2317
2318private:
2319 void addVec(const SkVector& vec) {
2320 SkASSERT(vec.fX || vec.fY);
2321 fVec0 = fVec1;
2322 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002323 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2324 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2325 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2326 largest = SkTMax(largest, -smallest);
2327 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002328 if (0 == fSign) {
2329 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330 if (1 == sign) {
2331 fDirection = SkPath::kCW_Direction;
2332 } else if (-1 == sign) {
2333 fDirection = SkPath::kCCW_Direction;
2334 }
reed@google.com04863fa2011-05-15 04:08:24 +00002335 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002336 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002337 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002339 }
2340 }
2341 }
2342
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002343 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002344 SkPoint fCurrPt;
2345 SkVector fVec0, fVec1, fFirstVec;
2346 int fPtCount; // non-degenerate points
2347 int fSign;
2348 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002349 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002350 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002351};
2352
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002353SkPath::Convexity SkPath::internalGetConvexity() const {
2354 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002355 SkPoint pts[4];
2356 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002358
2359 int contourCount = 0;
2360 int count;
2361 Convexicator state;
2362
2363 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2364 switch (verb) {
2365 case kMove_Verb:
2366 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002367 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002368 return kConcave_Convexity;
2369 }
2370 pts[1] = pts[0];
2371 count = 1;
2372 break;
2373 case kLine_Verb: count = 1; break;
2374 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002375 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002376 case kCubic_Verb: count = 3; break;
2377 case kClose_Verb:
2378 state.close();
2379 count = 0;
2380 break;
2381 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002382 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002383 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002384 return kConcave_Convexity;
2385 }
2386
2387 for (int i = 1; i <= count; i++) {
2388 state.addPt(pts[i]);
2389 }
2390 // early exit
2391 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002392 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002393 return kConcave_Convexity;
2394 }
2395 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002396 fConvexity = state.getConvexity();
2397 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2398 fDirection = state.getDirection();
2399 }
2400 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002401}
reed@google.com69a99432012-01-10 18:00:10 +00002402
2403///////////////////////////////////////////////////////////////////////////////
2404
2405class ContourIter {
2406public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002407 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002408
2409 bool done() const { return fDone; }
2410 // if !done() then these may be called
2411 int count() const { return fCurrPtCount; }
2412 const SkPoint* pts() const { return fCurrPt; }
2413 void next();
2414
2415private:
2416 int fCurrPtCount;
2417 const SkPoint* fCurrPt;
2418 const uint8_t* fCurrVerb;
2419 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002420 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002421 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002422 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002423};
2424
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002425ContourIter::ContourIter(const SkPathRef& pathRef) {
2426 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002427 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002428 fCurrPt = pathRef.points();
2429 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002430 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002431 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002432 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002433 this->next();
2434}
2435
2436void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002437 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002438 fDone = true;
2439 }
2440 if (fDone) {
2441 return;
2442 }
2443
2444 // skip pts of prev contour
2445 fCurrPt += fCurrPtCount;
2446
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002447 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002448 int ptCount = 1; // moveTo
2449 const uint8_t* verbs = fCurrVerb;
2450
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002451 for (--verbs; verbs > fStopVerbs; --verbs) {
2452 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002453 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002454 goto CONTOUR_END;
2455 case SkPath::kLine_Verb:
2456 ptCount += 1;
2457 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002458 case SkPath::kConic_Verb:
2459 fCurrConicWeight += 1;
2460 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002461 case SkPath::kQuad_Verb:
2462 ptCount += 2;
2463 break;
2464 case SkPath::kCubic_Verb:
2465 ptCount += 3;
2466 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002467 case SkPath::kClose_Verb:
2468 break;
2469 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002470 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002471 break;
2472 }
2473 }
2474CONTOUR_END:
2475 fCurrPtCount = ptCount;
2476 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002477 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002478}
2479
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002480// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002481static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002482 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2483 // We may get 0 when the above subtracts underflow. We expect this to be
2484 // very rare and lazily promote to double.
2485 if (0 == cross) {
2486 double p0x = SkScalarToDouble(p0.fX);
2487 double p0y = SkScalarToDouble(p0.fY);
2488
2489 double p1x = SkScalarToDouble(p1.fX);
2490 double p1y = SkScalarToDouble(p1.fY);
2491
2492 double p2x = SkScalarToDouble(p2.fX);
2493 double p2y = SkScalarToDouble(p2.fY);
2494
2495 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2496 (p1y - p0y) * (p2x - p0x));
2497
2498 }
2499 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002500}
2501
reed@google.comc1ea60a2012-01-31 15:15:36 +00002502// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002503static int find_max_y(const SkPoint pts[], int count) {
2504 SkASSERT(count > 0);
2505 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002506 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002507 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002508 SkScalar y = pts[i].fY;
2509 if (y > max) {
2510 max = y;
2511 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002512 }
2513 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002514 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002515}
2516
reed@google.comcabaf1d2012-01-11 21:03:05 +00002517static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2518 int i = index;
2519 for (;;) {
2520 i = (i + inc) % n;
2521 if (i == index) { // we wrapped around, so abort
2522 break;
2523 }
2524 if (pts[index] != pts[i]) { // found a different point, success!
2525 break;
2526 }
2527 }
2528 return i;
2529}
2530
reed@google.comc1ea60a2012-01-31 15:15:36 +00002531/**
2532 * Starting at index, and moving forward (incrementing), find the xmin and
2533 * xmax of the contiguous points that have the same Y.
2534 */
2535static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2536 int* maxIndexPtr) {
2537 const SkScalar y = pts[index].fY;
2538 SkScalar min = pts[index].fX;
2539 SkScalar max = min;
2540 int minIndex = index;
2541 int maxIndex = index;
2542 for (int i = index + 1; i < n; ++i) {
2543 if (pts[i].fY != y) {
2544 break;
2545 }
2546 SkScalar x = pts[i].fX;
2547 if (x < min) {
2548 min = x;
2549 minIndex = i;
2550 } else if (x > max) {
2551 max = x;
2552 maxIndex = i;
2553 }
2554 }
2555 *maxIndexPtr = maxIndex;
2556 return minIndex;
2557}
2558
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002559static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002560 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002561}
2562
reed@google.comac8543f2012-01-30 20:51:25 +00002563/*
2564 * We loop through all contours, and keep the computed cross-product of the
2565 * contour that contained the global y-max. If we just look at the first
2566 * contour, we may find one that is wound the opposite way (correctly) since
2567 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2568 * that is outer most (or at least has the global y-max) before we can consider
2569 * its cross product.
2570 */
reed@google.com69a99432012-01-10 18:00:10 +00002571bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002572 if (kUnknown_Direction != fDirection) {
2573 *dir = static_cast<Direction>(fDirection);
2574 return true;
2575 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002576
2577 // don't want to pay the cost for computing this if it
2578 // is unknown, so we don't call isConvex()
2579 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2580 SkASSERT(kUnknown_Direction == fDirection);
2581 *dir = static_cast<Direction>(fDirection);
2582 return false;
2583 }
reed@google.com69a99432012-01-10 18:00:10 +00002584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002585 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002586
reed@google.comac8543f2012-01-30 20:51:25 +00002587 // initialize with our logical y-min
2588 SkScalar ymax = this->getBounds().fTop;
2589 SkScalar ymaxCross = 0;
2590
reed@google.com69a99432012-01-10 18:00:10 +00002591 for (; !iter.done(); iter.next()) {
2592 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002593 if (n < 3) {
2594 continue;
2595 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002596
reed@google.comcabaf1d2012-01-11 21:03:05 +00002597 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002598 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002599 int index = find_max_y(pts, n);
2600 if (pts[index].fY < ymax) {
2601 continue;
2602 }
2603
2604 // If there is more than 1 distinct point at the y-max, we take the
2605 // x-min and x-max of them and just subtract to compute the dir.
2606 if (pts[(index + 1) % n].fY == pts[index].fY) {
2607 int maxIndex;
2608 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2609 if (minIndex == maxIndex) {
2610 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002611 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002612 SkASSERT(pts[minIndex].fY == pts[index].fY);
2613 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2614 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2615 // we just subtract the indices, and let that auto-convert to
2616 // SkScalar, since we just want - or + to signal the direction.
2617 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002618 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002619 TRY_CROSSPROD:
2620 // Find a next and prev index to use for the cross-product test,
2621 // but we try to find pts that form non-zero vectors from pts[index]
2622 //
2623 // Its possible that we can't find two non-degenerate vectors, so
2624 // we have to guard our search (e.g. all the pts could be in the
2625 // same place).
2626
2627 // we pass n - 1 instead of -1 so we don't foul up % operator by
2628 // passing it a negative LH argument.
2629 int prev = find_diff_pt(pts, index, n, n - 1);
2630 if (prev == index) {
2631 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002632 continue;
2633 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002634 int next = find_diff_pt(pts, index, n, 1);
2635 SkASSERT(next != index);
2636 cross = cross_prod(pts[prev], pts[index], pts[next]);
2637 // if we get a zero and the points are horizontal, then we look at the spread in
2638 // x-direction. We really should continue to walk away from the degeneracy until
2639 // there is a divergence.
2640 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2641 // construct the subtract so we get the correct Direction below
2642 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002643 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002644 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002645
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002646 if (cross) {
2647 // record our best guess so far
2648 ymax = pts[index].fY;
2649 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002650 }
2651 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002652 if (ymaxCross) {
2653 crossToDir(ymaxCross, dir);
2654 fDirection = *dir;
2655 return true;
2656 } else {
2657 return false;
2658 }
reed@google.comac8543f2012-01-30 20:51:25 +00002659}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002660
2661///////////////////////////////////////////////////////////////////////////////
2662
2663static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2664 SkScalar D, SkScalar t) {
2665 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2666}
2667
2668static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2669 SkScalar t) {
2670 SkScalar A = c3 + 3*(c1 - c2) - c0;
2671 SkScalar B = 3*(c2 - c1 - c1 + c0);
2672 SkScalar C = 3*(c1 - c0);
2673 SkScalar D = c0;
2674 return eval_cubic_coeff(A, B, C, D, t);
2675}
2676
2677/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2678 t value such that cubic(t) = target
2679 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002680static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 SkScalar target, SkScalar* t) {
2682 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2683 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002684
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002685 SkScalar D = c0 - target;
2686 SkScalar A = c3 + 3*(c1 - c2) - c0;
2687 SkScalar B = 3*(c2 - c1 - c1 + c0);
2688 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002689
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2691 SkScalar minT = 0;
2692 SkScalar maxT = SK_Scalar1;
2693 SkScalar mid;
2694 int i;
2695 for (i = 0; i < 16; i++) {
2696 mid = SkScalarAve(minT, maxT);
2697 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2698 if (delta < 0) {
2699 minT = mid;
2700 delta = -delta;
2701 } else {
2702 maxT = mid;
2703 }
2704 if (delta < TOLERANCE) {
2705 break;
2706 }
2707 }
2708 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002709}
2710
2711template <size_t N> static void find_minmax(const SkPoint pts[],
2712 SkScalar* minPtr, SkScalar* maxPtr) {
2713 SkScalar min, max;
2714 min = max = pts[0].fX;
2715 for (size_t i = 1; i < N; ++i) {
2716 min = SkMinScalar(min, pts[i].fX);
2717 max = SkMaxScalar(max, pts[i].fX);
2718 }
2719 *minPtr = min;
2720 *maxPtr = max;
2721}
2722
2723static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2724 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002726 int dir = 1;
2727 if (pts[0].fY > pts[3].fY) {
2728 storage[0] = pts[3];
2729 storage[1] = pts[2];
2730 storage[2] = pts[1];
2731 storage[3] = pts[0];
2732 pts = storage;
2733 dir = -1;
2734 }
2735 if (y < pts[0].fY || y >= pts[3].fY) {
2736 return 0;
2737 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002738
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002739 // quickreject or quickaccept
2740 SkScalar min, max;
2741 find_minmax<4>(pts, &min, &max);
2742 if (x < min) {
2743 return 0;
2744 }
2745 if (x > max) {
2746 return dir;
2747 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002750 SkScalar t;
2751 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2752 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 +00002753 return xt < x ? dir : 0;
2754}
2755
2756static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2757 SkPoint dst[10];
2758 int n = SkChopCubicAtYExtrema(pts, dst);
2759 int w = 0;
2760 for (int i = 0; i <= n; ++i) {
2761 w += winding_mono_cubic(&dst[i * 3], x, y);
2762 }
2763 return w;
2764}
2765
2766static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2767 SkScalar y0 = pts[0].fY;
2768 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002769
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002770 int dir = 1;
2771 if (y0 > y2) {
2772 SkTSwap(y0, y2);
2773 dir = -1;
2774 }
2775 if (y < y0 || y >= y2) {
2776 return 0;
2777 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002778
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002779 // bounds check on X (not required. is it faster?)
2780#if 0
2781 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2782 return 0;
2783 }
2784#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 SkScalar roots[2];
2787 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2788 2 * (pts[1].fY - pts[0].fY),
2789 pts[0].fY - y,
2790 roots);
2791 SkASSERT(n <= 1);
2792 SkScalar xt;
2793 if (0 == n) {
2794 SkScalar mid = SkScalarAve(y0, y2);
2795 // Need [0] and [2] if dir == 1
2796 // and [2] and [0] if dir == -1
2797 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2798 } else {
2799 SkScalar t = roots[0];
2800 SkScalar C = pts[0].fX;
2801 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2802 SkScalar B = 2 * (pts[1].fX - C);
2803 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2804 }
2805 return xt < x ? dir : 0;
2806}
2807
2808static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2809 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2810 if (y0 == y1) {
2811 return true;
2812 }
2813 if (y0 < y1) {
2814 return y1 <= y2;
2815 } else {
2816 return y1 >= y2;
2817 }
2818}
2819
2820static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2821 SkPoint dst[5];
2822 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2825 n = SkChopQuadAtYExtrema(pts, dst);
2826 pts = dst;
2827 }
2828 int w = winding_mono_quad(pts, x, y);
2829 if (n > 0) {
2830 w += winding_mono_quad(&pts[2], x, y);
2831 }
2832 return w;
2833}
2834
2835static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2836 SkScalar x0 = pts[0].fX;
2837 SkScalar y0 = pts[0].fY;
2838 SkScalar x1 = pts[1].fX;
2839 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002842
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 int dir = 1;
2844 if (y0 > y1) {
2845 SkTSwap(y0, y1);
2846 dir = -1;
2847 }
2848 if (y < y0 || y >= y1) {
2849 return 0;
2850 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2853 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002854
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 if (SkScalarSignAsInt(cross) == dir) {
2856 dir = 0;
2857 }
2858 return dir;
2859}
2860
reed@google.com4db592c2013-10-30 17:39:43 +00002861static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2862 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2863}
2864
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865bool SkPath::contains(SkScalar x, SkScalar y) const {
2866 bool isInverse = this->isInverseFillType();
2867 if (this->isEmpty()) {
2868 return isInverse;
2869 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002870
reed@google.com4db592c2013-10-30 17:39:43 +00002871 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 return isInverse;
2873 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 SkPath::Iter iter(*this, true);
2876 bool done = false;
2877 int w = 0;
2878 do {
2879 SkPoint pts[4];
2880 switch (iter.next(pts, false)) {
2881 case SkPath::kMove_Verb:
2882 case SkPath::kClose_Verb:
2883 break;
2884 case SkPath::kLine_Verb:
2885 w += winding_line(pts, x, y);
2886 break;
2887 case SkPath::kQuad_Verb:
2888 w += winding_quad(pts, x, y);
2889 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002890 case SkPath::kConic_Verb:
2891 SkASSERT(0);
2892 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002893 case SkPath::kCubic_Verb:
2894 w += winding_cubic(pts, x, y);
2895 break;
2896 case SkPath::kDone_Verb:
2897 done = true;
2898 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002899 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002900 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002901
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002902 switch (this->getFillType()) {
2903 case SkPath::kEvenOdd_FillType:
2904 case SkPath::kInverseEvenOdd_FillType:
2905 w &= 1;
2906 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002907 default:
2908 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002909 }
2910 return SkToBool(w);
2911}