blob: 00d3772b57cdebe63227910c9b6183959b13b8a7 [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
10#include "SkPath.h"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
14#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000016SK_DEFINE_INST_COUNT(SkPath);
17
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.com6aa29652012-04-18 13:29:52 +000043class SkAutoDisableOvalCheck {
44public:
45 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
46 fSaved = fPath->fIsOval;
47 }
48
49 ~SkAutoDisableOvalCheck() {
50 fPath->fIsOval = fSaved;
51 }
52
53private:
54 SkPath* fPath;
55 bool fSaved;
56};
57
reed@android.com8a1c16f2008-12-17 15:59:43 +000058/* This guy's constructor/destructor bracket a path editing operation. It is
59 used when we know the bounds of the amount we are going to add to the path
60 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000061
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 It captures some state about the path up front (i.e. if it already has a
63 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000064 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000065
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000066 It also notes if the path was originally degenerate, and if so, sets
67 isConvex to true. Thus it can only be used if the contour being added is
68 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 */
70class SkAutoPathBoundsUpdate {
71public:
72 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
73 this->init(path);
74 }
75
76 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
77 SkScalar right, SkScalar bottom) {
78 fRect.set(left, top, right, bottom);
79 this->init(path);
80 }
reed@google.comabf15c12011-01-18 20:35:51 +000081
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000085 fPath->fBounds = fRect;
86 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000087 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000089 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000090 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000091 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 }
93 }
reed@google.comabf15c12011-01-18 20:35:51 +000094
reed@android.com8a1c16f2008-12-17 15:59:43 +000095private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000096 SkPath* fPath;
97 SkRect fRect;
98 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000099 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000100 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000101
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000103 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000105 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000106 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000108 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000109 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 }
111};
112
reed@google.com0bb18bb2012-07-26 15:20:36 +0000113// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000114static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
115 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000116 if (count <= 1) { // we ignore just 1 point (moveto)
117 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000118 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000120 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 }
122}
123
124////////////////////////////////////////////////////////////////////////////
125
126/*
127 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
130
131 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000132 1. If we encounter degenerate segments, remove them
133 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
134 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
135 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136*/
137
138////////////////////////////////////////////////////////////////////////////
139
reed@google.comd335d1d2012-01-12 18:17:11 +0000140// flag to require a moveTo if we begin with something else, like lineTo etc.
141#define INITIAL_LASTMOVETOINDEX_VALUE ~0
142
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000143SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000144 : fPathRef(SkPathRef::CreateEmpty())
145 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000146 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000147 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000148 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000149 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000150 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000151 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000152#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000153 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000154 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000155#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000156}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157
158SkPath::SkPath(const SkPath& src) {
159 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000160 src.fPathRef.get()->ref();
161 fPathRef.reset(src.fPathRef.get());
162 fBounds = src.fBounds;
163 fFillType = src.fFillType;
164 fBoundsIsDirty = src.fBoundsIsDirty;
165 fConvexity = src.fConvexity;
166 fIsFinite = src.fIsFinite;
167 fSegmentMask = src.fSegmentMask;
168 fLastMoveToIndex = src.fLastMoveToIndex;
169 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000170#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000171 fGenerationID = src.fGenerationID;
172 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000173#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174}
175
176SkPath::~SkPath() {
177 SkDEBUGCODE(this->validate();)
178}
179
180SkPath& SkPath::operator=(const SkPath& src) {
181 SkDEBUGCODE(src.validate();)
182
183 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000184 src.fPathRef.get()->ref();
185 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000186 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000187 fFillType = src.fFillType;
188 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000189 fConvexity = src.fConvexity;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000190 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000191 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000192 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000193 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000194 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 }
196 SkDEBUGCODE(this->validate();)
197 return *this;
198}
199
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000200SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000201 // note: don't need to look at isConvex or bounds, since just comparing the
202 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000203
204 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
205 // since it is only a cache of info in the fVerbs, but its a fast way to
206 // notice a difference
207
reed@android.com3abec1d2009-03-02 05:36:20 +0000208 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000209 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000210 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000211}
212
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213void SkPath::swap(SkPath& other) {
214 SkASSERT(&other != NULL);
215
216 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000217 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000218 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000220 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000221 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000222 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000223 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000224 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000225 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000226 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 }
228}
229
djsollen@google.com56c69772011-11-08 19:00:26 +0000230#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000231uint32_t SkPath::getGenerationID() const {
232 return fGenerationID;
233}
djsollen@google.come63793a2012-03-21 15:39:03 +0000234
235const SkPath* SkPath::getSourcePath() const {
236 return fSourcePath;
237}
238
239void SkPath::setSourcePath(const SkPath* path) {
240 fSourcePath = path;
241}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000242#endif
243
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244void SkPath::reset() {
245 SkDEBUGCODE(this->validate();)
246
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000247 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000248 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000249 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000250 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000251 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000252 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000253 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254}
255
256void SkPath::rewind() {
257 SkDEBUGCODE(this->validate();)
258
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000259 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000260 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000261 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000262 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000263 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000264 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000265 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266}
267
268bool SkPath::isEmpty() const {
269 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000270 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271}
272
reed@google.com7e6c4d12012-05-10 14:05:43 +0000273bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000274 int verbCount = fPathRef->countVerbs();
275 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000276
reed@google.com7e6c4d12012-05-10 14:05:43 +0000277 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000278 if (kMove_Verb == fPathRef->atVerb(0) &&
279 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000280 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000281 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000282 line[0] = pts[0];
283 line[1] = pts[1];
284 }
285 return true;
286 }
287 }
288 return false;
289}
290
caryclark@google.comf1316942011-07-26 19:54:45 +0000291/*
292 Determines if path is a rect by keeping track of changes in direction
293 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000294
caryclark@google.comf1316942011-07-26 19:54:45 +0000295 The direction is computed such that:
296 0: vertical up
297 1: horizontal right
298 2: vertical down
299 3: horizontal left
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000300
caryclark@google.comf1316942011-07-26 19:54:45 +0000301A rectangle cycles up/right/down/left or up/left/down/right.
302
303The test fails if:
304 The path is closed, and followed by a line.
305 A second move creates a new endpoint.
306 A diagonal line is parsed.
307 There's more than four changes of direction.
308 There's a discontinuity on the line (e.g., a move in the middle)
309 The line reverses direction.
310 The rectangle doesn't complete a cycle.
311 The path contains a quadratic or cubic.
312 The path contains fewer than four points.
313 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000314
caryclark@google.comf1316942011-07-26 19:54:45 +0000315It's OK if the path has:
316 Several colinear line segments composing a rectangle side.
317 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000318
caryclark@google.comf1316942011-07-26 19:54:45 +0000319The direction takes advantage of the corners found since opposite sides
320must travel in opposite directions.
321
322FIXME: Allow colinear quads and cubics to be treated like lines.
323FIXME: If the API passes fill-only, return true if the filled stroke
324 is a rectangle, though the caller failed to close the path.
325 */
326bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000328
caryclark@google.comf1316942011-07-26 19:54:45 +0000329 int corners = 0;
330 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000331 first.set(0, 0);
332 last.set(0, 0);
333 int firstDirection = 0;
334 int lastDirection = 0;
335 int nextDirection = 0;
336 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000337 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000338 const SkPoint* pts = fPathRef->points();
339 int verbCnt = fPathRef->countVerbs();
340 int currVerb = 0;
341 while (currVerb < verbCnt) {
342 switch (fPathRef->atVerb(currVerb++)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000343 case kClose_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000344 pts = fPathRef->points();
caryclark@google.comf1316942011-07-26 19:54:45 +0000345 autoClose = true;
346 case kLine_Verb: {
347 SkScalar left = last.fX;
348 SkScalar top = last.fY;
349 SkScalar right = pts->fX;
350 SkScalar bottom = pts->fY;
351 ++pts;
352 if (left != right && top != bottom) {
353 return false; // diagonal
354 }
355 if (left == right && top == bottom) {
356 break; // single point on side OK
357 }
358 nextDirection = (left != right) << 0 |
359 (left < right || top < bottom) << 1;
360 if (0 == corners) {
361 firstDirection = nextDirection;
362 first = last;
363 last = pts[-1];
364 corners = 1;
365 closedOrMoved = false;
366 break;
367 }
368 if (closedOrMoved) {
369 return false; // closed followed by a line
370 }
371 closedOrMoved = autoClose;
372 if (lastDirection != nextDirection) {
373 if (++corners > 4) {
374 return false; // too many direction changes
375 }
376 }
377 last = pts[-1];
378 if (lastDirection == nextDirection) {
379 break; // colinear segment
380 }
381 // Possible values for corners are 2, 3, and 4.
382 // When corners == 3, nextDirection opposes firstDirection.
383 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000384 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000385 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
386 if ((directionCycle ^ turn) != nextDirection) {
387 return false; // direction didn't follow cycle
388 }
389 break;
390 }
391 case kQuad_Verb:
392 case kCubic_Verb:
393 return false; // quadratic, cubic not allowed
394 case kMove_Verb:
395 last = *pts++;
396 closedOrMoved = true;
397 break;
398 }
399 lastDirection = nextDirection;
400 }
401 // Success if 4 corners and first point equals last
402 bool result = 4 == corners && first == last;
403 if (result && rect) {
404 *rect = getBounds();
405 }
406 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407}
408
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000409int SkPath::countPoints() const {
410 return fPathRef->countPoints();
411}
412
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000413int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000414 SkDEBUGCODE(this->validate();)
415
416 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000417 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000418 int count = SkMin32(max, fPathRef->countPoints());
419 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
420 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421}
422
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000423SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000424 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
425 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000426 }
427 return SkPoint::Make(0, 0);
428}
429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000430int SkPath::countVerbs() const {
431 return fPathRef->countVerbs();
432}
433
434static inline void copy_verbs_reverse(uint8_t* inorderDst,
435 const uint8_t* reversedSrc,
436 int count) {
437 for (int i = 0; i < count; ++i) {
438 inorderDst[i] = reversedSrc[~i];
439 }
440}
441
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000442int SkPath::getVerbs(uint8_t dst[], int max) const {
443 SkDEBUGCODE(this->validate();)
444
445 SkASSERT(max >= 0);
446 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000447 int count = SkMin32(max, fPathRef->countVerbs());
448 copy_verbs_reverse(dst, fPathRef->verbs(), count);
449 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000450}
451
reed@google.com294dd7b2011-10-11 11:58:32 +0000452bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453 SkDEBUGCODE(this->validate();)
454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000455 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000456 if (count > 0) {
457 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000458 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000459 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000460 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000462 if (lastPt) {
463 lastPt->set(0, 0);
464 }
465 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466}
467
468void SkPath::setLastPt(SkScalar x, SkScalar y) {
469 SkDEBUGCODE(this->validate();)
470
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000471 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472 if (count == 0) {
473 this->moveTo(x, y);
474 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000475 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000476 SkPathRef::Editor ed(&fPathRef);
477 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000478 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 }
480}
481
reed@android.comd252db02009-04-01 18:31:44 +0000482void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000483 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000484 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000486 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000487 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488}
489
reed@google.com04863fa2011-05-15 04:08:24 +0000490void SkPath::setConvexity(Convexity c) {
491 if (fConvexity != c) {
492 fConvexity = c;
493 GEN_ID_INC;
494 }
495}
496
reed@android.com8a1c16f2008-12-17 15:59:43 +0000497//////////////////////////////////////////////////////////////////////////////
498// Construction methods
499
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000500#define DIRTY_AFTER_EDIT \
501 do { \
502 fBoundsIsDirty = true; \
503 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000504 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000505 } while (0)
506
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000507#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
508 do { \
509 fBoundsIsDirty = true; \
510 } while (0)
511
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512void SkPath::incReserve(U16CPU inc) {
513 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000514 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 SkDEBUGCODE(this->validate();)
516}
517
518void SkPath::moveTo(SkScalar x, SkScalar y) {
519 SkDEBUGCODE(this->validate();)
520
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000521 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522
reed@google.comd335d1d2012-01-12 18:17:11 +0000523 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000524 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000526 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000528 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000529 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000530}
531
532void SkPath::rMoveTo(SkScalar x, SkScalar y) {
533 SkPoint pt;
534 this->getLastPt(&pt);
535 this->moveTo(pt.fX + x, pt.fY + y);
536}
537
reed@google.comd335d1d2012-01-12 18:17:11 +0000538void SkPath::injectMoveToIfNeeded() {
539 if (fLastMoveToIndex < 0) {
540 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000541 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000542 x = y = 0;
543 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000544 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000545 x = pt.fX;
546 y = pt.fY;
547 }
548 this->moveTo(x, y);
549 }
550}
551
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552void SkPath::lineTo(SkScalar x, SkScalar y) {
553 SkDEBUGCODE(this->validate();)
554
reed@google.comd335d1d2012-01-12 18:17:11 +0000555 this->injectMoveToIfNeeded();
556
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000557 SkPathRef::Editor ed(&fPathRef);
558 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000559 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000560
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000561 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000562 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563}
564
565void SkPath::rLineTo(SkScalar x, SkScalar y) {
566 SkPoint pt;
567 this->getLastPt(&pt);
568 this->lineTo(pt.fX + x, pt.fY + y);
569}
570
571void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
572 SkDEBUGCODE(this->validate();)
573
reed@google.comd335d1d2012-01-12 18:17:11 +0000574 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576 SkPathRef::Editor ed(&fPathRef);
577 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 pts[0].set(x1, y1);
579 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000580 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000582 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000583 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584}
585
586void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
587 SkPoint pt;
588 this->getLastPt(&pt);
589 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
590}
591
592void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
593 SkScalar x3, SkScalar y3) {
594 SkDEBUGCODE(this->validate();)
595
reed@google.comd335d1d2012-01-12 18:17:11 +0000596 this->injectMoveToIfNeeded();
597
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000598 SkPathRef::Editor ed(&fPathRef);
599 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 pts[0].set(x1, y1);
601 pts[1].set(x2, y2);
602 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000603 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000605 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000606 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607}
608
609void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
610 SkScalar x3, SkScalar y3) {
611 SkPoint pt;
612 this->getLastPt(&pt);
613 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
614 pt.fX + x3, pt.fY + y3);
615}
616
617void SkPath::close() {
618 SkDEBUGCODE(this->validate();)
619
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000620 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 case kLine_Verb:
624 case kQuad_Verb:
625 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 case kMove_Verb: {
627 SkPathRef::Editor ed(&fPathRef);
628 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000629 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000633 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 break;
635 }
636 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000637
638 // signal that we need a moveTo to follow us (unless we're done)
639#if 0
640 if (fLastMoveToIndex >= 0) {
641 fLastMoveToIndex = ~fLastMoveToIndex;
642 }
643#else
644 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
645#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
648///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000649
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650void SkPath::addRect(const SkRect& rect, Direction dir) {
651 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
652}
653
654void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
655 SkScalar bottom, Direction dir) {
656 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
657
658 this->incReserve(5);
659
660 this->moveTo(left, top);
661 if (dir == kCCW_Direction) {
662 this->lineTo(left, bottom);
663 this->lineTo(right, bottom);
664 this->lineTo(right, top);
665 } else {
666 this->lineTo(right, top);
667 this->lineTo(right, bottom);
668 this->lineTo(left, bottom);
669 }
670 this->close();
671}
672
reed@google.com744faba2012-05-29 19:54:52 +0000673void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
674 SkDEBUGCODE(this->validate();)
675 if (count <= 0) {
676 return;
677 }
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 SkPathRef::Editor ed(&fPathRef);
680 fLastMoveToIndex = ed.pathRef()->countPoints();
681 uint8_t* vb;
682 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000683 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000685
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 memcpy(p, pts, count * sizeof(SkPoint));
687 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000688 if (count > 1) {
689 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
690 // be 0, the compiler will remove the test/branch entirely.
691 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000693 } else {
694 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000696 }
697 }
698 fSegmentMask |= kLine_SegmentMask;
699 }
700 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000701 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000702 }
703
704 GEN_ID_INC;
705 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000706 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
710
711void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
712 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkScalar w = rect.width();
714 SkScalar halfW = SkScalarHalf(w);
715 SkScalar h = rect.height();
716 SkScalar halfH = SkScalarHalf(h);
717
718 if (halfW <= 0 || halfH <= 0) {
719 return;
720 }
721
reed@google.comabf15c12011-01-18 20:35:51 +0000722 bool skip_hori = rx >= halfW;
723 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724
725 if (skip_hori && skip_vert) {
726 this->addOval(rect, dir);
727 return;
728 }
reed@google.comabf15c12011-01-18 20:35:51 +0000729
730 SkAutoPathBoundsUpdate apbu(this, rect);
731
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732 if (skip_hori) {
733 rx = halfW;
734 } else if (skip_vert) {
735 ry = halfH;
736 }
737
738 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
739 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
740
741 this->incReserve(17);
742 this->moveTo(rect.fRight - rx, rect.fTop);
743 if (dir == kCCW_Direction) {
744 if (!skip_hori) {
745 this->lineTo(rect.fLeft + rx, rect.fTop); // top
746 }
747 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
748 rect.fLeft, rect.fTop + ry - sy,
749 rect.fLeft, rect.fTop + ry); // top-left
750 if (!skip_vert) {
751 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
752 }
753 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
754 rect.fLeft + rx - sx, rect.fBottom,
755 rect.fLeft + rx, rect.fBottom); // bot-left
756 if (!skip_hori) {
757 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
758 }
759 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
760 rect.fRight, rect.fBottom - ry + sy,
761 rect.fRight, rect.fBottom - ry); // bot-right
762 if (!skip_vert) {
763 this->lineTo(rect.fRight, rect.fTop + ry);
764 }
765 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
766 rect.fRight - rx + sx, rect.fTop,
767 rect.fRight - rx, rect.fTop); // top-right
768 } else {
769 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
770 rect.fRight, rect.fTop + ry - sy,
771 rect.fRight, rect.fTop + ry); // top-right
772 if (!skip_vert) {
773 this->lineTo(rect.fRight, rect.fBottom - ry);
774 }
775 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
776 rect.fRight - rx + sx, rect.fBottom,
777 rect.fRight - rx, rect.fBottom); // bot-right
778 if (!skip_hori) {
779 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
780 }
781 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
782 rect.fLeft, rect.fBottom - ry + sy,
783 rect.fLeft, rect.fBottom - ry); // bot-left
784 if (!skip_vert) {
785 this->lineTo(rect.fLeft, rect.fTop + ry); // left
786 }
787 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
788 rect.fLeft + rx - sx, rect.fTop,
789 rect.fLeft + rx, rect.fTop); // top-left
790 if (!skip_hori) {
791 this->lineTo(rect.fRight - rx, rect.fTop); // top
792 }
793 }
794 this->close();
795}
796
797static void add_corner_arc(SkPath* path, const SkRect& rect,
798 SkScalar rx, SkScalar ry, int startAngle,
799 SkPath::Direction dir, bool forceMoveTo) {
800 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
801 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000802
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 SkRect r;
804 r.set(-rx, -ry, rx, ry);
805
806 switch (startAngle) {
807 case 0:
808 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
809 break;
810 case 90:
811 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
812 break;
813 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
814 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000815 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 }
reed@google.comabf15c12011-01-18 20:35:51 +0000817
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818 SkScalar start = SkIntToScalar(startAngle);
819 SkScalar sweep = SkIntToScalar(90);
820 if (SkPath::kCCW_Direction == dir) {
821 start += sweep;
822 sweep = -sweep;
823 }
reed@google.comabf15c12011-01-18 20:35:51 +0000824
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 path->arcTo(r, start, sweep, forceMoveTo);
826}
827
828void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
829 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000830 // abort before we invoke SkAutoPathBoundsUpdate()
831 if (rect.isEmpty()) {
832 return;
833 }
834
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 SkAutoPathBoundsUpdate apbu(this, rect);
836
837 if (kCW_Direction == dir) {
838 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
839 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
840 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
841 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
842 } else {
843 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
844 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
845 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
846 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
847 }
848 this->close();
849}
850
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000851bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000852 int count = fPathRef->countVerbs();
853 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
854 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000855 if (*verbs == kLine_Verb ||
856 *verbs == kQuad_Verb ||
857 *verbs == kCubic_Verb) {
858 return false;
859 }
860 ++verbs;
861 }
862 return true;
863}
864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000866 /* If addOval() is called after previous moveTo(),
867 this path is still marked as an oval. This is used to
868 fit into WebKit's calling sequences.
869 We can't simply check isEmpty() in this case, as additional
870 moveTo() would mark the path non empty.
871 */
872 fIsOval = hasOnlyMoveTos();
873
874 SkAutoDisableOvalCheck adoc(this);
875
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 SkAutoPathBoundsUpdate apbu(this, oval);
877
878 SkScalar cx = oval.centerX();
879 SkScalar cy = oval.centerY();
880 SkScalar rx = SkScalarHalf(oval.width());
881 SkScalar ry = SkScalarHalf(oval.height());
882#if 0 // these seem faster than using quads (1/2 the number of edges)
883 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
884 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
885
886 this->incReserve(13);
887 this->moveTo(cx + rx, cy);
888 if (dir == kCCW_Direction) {
889 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
890 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
891 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
892 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
893 } else {
894 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
895 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
896 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
897 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
898 }
899#else
900 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
901 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
902 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
903 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
904
905 /*
906 To handle imprecision in computing the center and radii, we revert to
907 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
908 to ensure that we don't exceed the oval's bounds *ever*, since we want
909 to use oval for our fast-bounds, rather than have to recompute it.
910 */
911 const SkScalar L = oval.fLeft; // cx - rx
912 const SkScalar T = oval.fTop; // cy - ry
913 const SkScalar R = oval.fRight; // cx + rx
914 const SkScalar B = oval.fBottom; // cy + ry
915
916 this->incReserve(17); // 8 quads + close
917 this->moveTo(R, cy);
918 if (dir == kCCW_Direction) {
919 this->quadTo( R, cy - sy, cx + mx, cy - my);
920 this->quadTo(cx + sx, T, cx , T);
921 this->quadTo(cx - sx, T, cx - mx, cy - my);
922 this->quadTo( L, cy - sy, L, cy );
923 this->quadTo( L, cy + sy, cx - mx, cy + my);
924 this->quadTo(cx - sx, B, cx , B);
925 this->quadTo(cx + sx, B, cx + mx, cy + my);
926 this->quadTo( R, cy + sy, R, cy );
927 } else {
928 this->quadTo( R, cy + sy, cx + mx, cy + my);
929 this->quadTo(cx + sx, B, cx , B);
930 this->quadTo(cx - sx, B, cx - mx, cy + my);
931 this->quadTo( L, cy + sy, L, cy );
932 this->quadTo( L, cy - sy, cx - mx, cy - my);
933 this->quadTo(cx - sx, T, cx , T);
934 this->quadTo(cx + sx, T, cx + mx, cy - my);
935 this->quadTo( R, cy - sy, R, cy );
936 }
937#endif
938 this->close();
939}
940
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000941bool SkPath::isOval(SkRect* rect) const {
942 if (fIsOval && rect) {
943 *rect = getBounds();
944 }
945
946 return fIsOval;
947}
948
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
950 if (r > 0) {
951 SkRect rect;
952 rect.set(x - r, y - r, x + r, y + r);
953 this->addOval(rect, dir);
954 }
955}
956
957#include "SkGeometry.h"
958
959static int build_arc_points(const SkRect& oval, SkScalar startAngle,
960 SkScalar sweepAngle,
961 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +0000962
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000963 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +0000964 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
965 // Chrome uses this path to move into and out of ovals. If not
966 // treated as a special case the moves can distort the oval's
967 // bounding box (and break the circle special case).
968 pts[0].set(oval.fRight, oval.centerY());
969 return 1;
970 }
971
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 SkVector start, stop;
973
974 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
975 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
976 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000977
978 /* If the sweep angle is nearly (but less than) 360, then due to precision
979 loss in radians-conversion and/or sin/cos, we may end up with coincident
980 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
981 of drawing a nearly complete circle (good).
982 e.g. canvas.drawArc(0, 359.99, ...)
983 -vs- canvas.drawArc(0, 359.9, ...)
984 We try to detect this edge case, and tweak the stop vector
985 */
986 if (start == stop) {
987 SkScalar sw = SkScalarAbs(sweepAngle);
988 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
989 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
990 // make a guess at a tiny angle (in radians) to tweak by
991 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
992 // not sure how much will be enough, so we use a loop
993 do {
994 stopRad -= deltaRad;
995 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
996 } while (start == stop);
997 }
998 }
999
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001001
reed@android.com8a1c16f2008-12-17 15:59:43 +00001002 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1003 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001004
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 return SkBuildQuadArc(start, stop,
1006 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1007 &matrix, pts);
1008}
1009
1010void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1011 bool forceMoveTo) {
1012 if (oval.width() < 0 || oval.height() < 0) {
1013 return;
1014 }
1015
1016 SkPoint pts[kSkBuildQuadArcStorage];
1017 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1018 SkASSERT((count & 1) == 1);
1019
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001020 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 forceMoveTo = true;
1022 }
1023 this->incReserve(count);
1024 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1025 for (int i = 1; i < count; i += 2) {
1026 this->quadTo(pts[i], pts[i+1]);
1027 }
1028}
1029
1030void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1031 SkScalar sweepAngle) {
1032 if (oval.isEmpty() || 0 == sweepAngle) {
1033 return;
1034 }
1035
1036 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1037
1038 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1039 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1040 return;
1041 }
1042
1043 SkPoint pts[kSkBuildQuadArcStorage];
1044 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1045
1046 this->incReserve(count);
1047 this->moveTo(pts[0]);
1048 for (int i = 1; i < count; i += 2) {
1049 this->quadTo(pts[i], pts[i+1]);
1050 }
1051}
1052
1053/*
1054 Need to handle the case when the angle is sharp, and our computed end-points
1055 for the arc go behind pt1 and/or p2...
1056*/
1057void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1058 SkScalar radius) {
1059 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001060
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 // need to know our prev pt so we can construct tangent vectors
1062 {
1063 SkPoint start;
1064 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001065 // Handle degenerate cases by adding a line to the first point and
1066 // bailing out.
1067 if ((x1 == start.fX && y1 == start.fY) ||
1068 (x1 == x2 && y1 == y2) ||
1069 radius == 0) {
1070 this->lineTo(x1, y1);
1071 return;
1072 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073 before.setNormalize(x1 - start.fX, y1 - start.fY);
1074 after.setNormalize(x2 - x1, y2 - y1);
1075 }
reed@google.comabf15c12011-01-18 20:35:51 +00001076
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 SkScalar cosh = SkPoint::DotProduct(before, after);
1078 SkScalar sinh = SkPoint::CrossProduct(before, after);
1079
1080 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001081 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 return;
1083 }
reed@google.comabf15c12011-01-18 20:35:51 +00001084
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1086 if (dist < 0) {
1087 dist = -dist;
1088 }
1089
1090 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1091 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1092 SkRotationDirection arcDir;
1093
1094 // now turn before/after into normals
1095 if (sinh > 0) {
1096 before.rotateCCW();
1097 after.rotateCCW();
1098 arcDir = kCW_SkRotationDirection;
1099 } else {
1100 before.rotateCW();
1101 after.rotateCW();
1102 arcDir = kCCW_SkRotationDirection;
1103 }
1104
1105 SkMatrix matrix;
1106 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001107
reed@android.com8a1c16f2008-12-17 15:59:43 +00001108 matrix.setScale(radius, radius);
1109 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1110 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001111
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001113
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 this->incReserve(count);
1115 // [xx,yy] == pts[0]
1116 this->lineTo(xx, yy);
1117 for (int i = 1; i < count; i += 2) {
1118 this->quadTo(pts[i], pts[i+1]);
1119 }
1120}
1121
1122///////////////////////////////////////////////////////////////////////////////
1123
1124void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1125 SkMatrix matrix;
1126
1127 matrix.setTranslate(dx, dy);
1128 this->addPath(path, matrix);
1129}
1130
1131void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001132 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001134 fIsOval = false;
1135
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001136 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 SkPoint pts[4];
1138 Verb verb;
1139
1140 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1141
1142 while ((verb = iter.next(pts)) != kDone_Verb) {
1143 switch (verb) {
1144 case kMove_Verb:
1145 proc(matrix, &pts[0], &pts[0], 1);
1146 this->moveTo(pts[0]);
1147 break;
1148 case kLine_Verb:
1149 proc(matrix, &pts[1], &pts[1], 1);
1150 this->lineTo(pts[1]);
1151 break;
1152 case kQuad_Verb:
1153 proc(matrix, &pts[1], &pts[1], 2);
1154 this->quadTo(pts[1], pts[2]);
1155 break;
1156 case kCubic_Verb:
1157 proc(matrix, &pts[1], &pts[1], 3);
1158 this->cubicTo(pts[1], pts[2], pts[3]);
1159 break;
1160 case kClose_Verb:
1161 this->close();
1162 break;
1163 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001164 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 }
1166 }
1167}
1168
1169///////////////////////////////////////////////////////////////////////////////
1170
1171static const uint8_t gPtsInVerb[] = {
1172 1, // kMove
1173 1, // kLine
1174 2, // kQuad
1175 3, // kCubic
1176 0, // kClose
1177 0 // kDone
1178};
1179
1180// ignore the initial moveto, and stop when the 1st contour ends
1181void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001182 int i, vcount = path.fPathRef->countVerbs();
1183 // exit early if the path is empty, or just has a moveTo.
1184 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001185 return;
1186 }
1187
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001188 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190 fIsOval = false;
1191
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001192 const uint8_t* verbs = path.fPathRef->verbs();
1193 // skip the initial moveTo
1194 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001196 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001198 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 case kLine_Verb:
1200 this->lineTo(pts[0].fX, pts[0].fY);
1201 break;
1202 case kQuad_Verb:
1203 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1204 break;
1205 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001206 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 break;
1208 case kClose_Verb:
1209 return;
1210 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001211 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212 }
1213}
1214
1215// ignore the last point of the 1st contour
1216void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001217 int i, vcount = path.fPathRef->countVerbs();
1218 // exit early if the path is empty, or just has a moveTo.
1219 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 return;
1221 }
1222
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001223 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001225 fIsOval = false;
1226
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001227 const uint8_t* verbs = path.fPathRef->verbs();
1228 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001230 SkASSERT(verbs[~0] == kMove_Verb);
1231 for (i = 1; i < vcount; ++i) {
1232 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 if (n == 0) {
1234 break;
1235 }
1236 pts += n;
1237 }
1238
1239 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001240 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 case kLine_Verb:
1242 this->lineTo(pts[-1].fX, pts[-1].fY);
1243 break;
1244 case kQuad_Verb:
1245 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1246 break;
1247 case kCubic_Verb:
1248 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1249 pts[-3].fX, pts[-3].fY);
1250 break;
1251 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001252 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 break;
1254 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001255 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 }
1257}
1258
reed@google.com63d73742012-01-10 15:33:12 +00001259void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001260 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001261
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001262 const SkPoint* pts = src.fPathRef->pointsEnd();
1263 // we will iterator through src's verbs backwards
1264 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1265 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001266
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001267 fIsOval = false;
1268
reed@google.com63d73742012-01-10 15:33:12 +00001269 bool needMove = true;
1270 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001271 while (verbs < verbsEnd) {
1272 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001273 int n = gPtsInVerb[v];
1274
1275 if (needMove) {
1276 --pts;
1277 this->moveTo(pts->fX, pts->fY);
1278 needMove = false;
1279 }
1280 pts -= n;
1281 switch (v) {
1282 case kMove_Verb:
1283 if (needClose) {
1284 this->close();
1285 needClose = false;
1286 }
1287 needMove = true;
1288 pts += 1; // so we see the point in "if (needMove)" above
1289 break;
1290 case kLine_Verb:
1291 this->lineTo(pts[0]);
1292 break;
1293 case kQuad_Verb:
1294 this->quadTo(pts[1], pts[0]);
1295 break;
1296 case kCubic_Verb:
1297 this->cubicTo(pts[2], pts[1], pts[0]);
1298 break;
1299 case kClose_Verb:
1300 needClose = true;
1301 break;
1302 default:
1303 SkASSERT(!"unexpected verb");
1304 }
1305 }
1306}
1307
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308///////////////////////////////////////////////////////////////////////////////
1309
1310void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1311 SkMatrix matrix;
1312
1313 matrix.setTranslate(dx, dy);
1314 this->transform(matrix, dst);
1315}
1316
1317#include "SkGeometry.h"
1318
1319static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1320 int level = 2) {
1321 if (--level >= 0) {
1322 SkPoint tmp[5];
1323
1324 SkChopQuadAtHalf(pts, tmp);
1325 subdivide_quad_to(path, &tmp[0], level);
1326 subdivide_quad_to(path, &tmp[2], level);
1327 } else {
1328 path->quadTo(pts[1], pts[2]);
1329 }
1330}
1331
1332static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1333 int level = 2) {
1334 if (--level >= 0) {
1335 SkPoint tmp[7];
1336
1337 SkChopCubicAtHalf(pts, tmp);
1338 subdivide_cubic_to(path, &tmp[0], level);
1339 subdivide_cubic_to(path, &tmp[3], level);
1340 } else {
1341 path->cubicTo(pts[1], pts[2], pts[3]);
1342 }
1343}
1344
1345void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1346 SkDEBUGCODE(this->validate();)
1347 if (dst == NULL) {
1348 dst = (SkPath*)this;
1349 }
1350
tomhudson@google.com8d430182011-06-06 19:11:19 +00001351 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352 SkPath tmp;
1353 tmp.fFillType = fFillType;
1354
1355 SkPath::Iter iter(*this, false);
1356 SkPoint pts[4];
1357 SkPath::Verb verb;
1358
reed@google.com4a3b7142012-05-16 17:16:46 +00001359 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 switch (verb) {
1361 case kMove_Verb:
1362 tmp.moveTo(pts[0]);
1363 break;
1364 case kLine_Verb:
1365 tmp.lineTo(pts[1]);
1366 break;
1367 case kQuad_Verb:
1368 subdivide_quad_to(&tmp, pts);
1369 break;
1370 case kCubic_Verb:
1371 subdivide_cubic_to(&tmp, pts);
1372 break;
1373 case kClose_Verb:
1374 tmp.close();
1375 break;
1376 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001377 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 break;
1379 }
1380 }
1381
1382 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001383 SkPathRef::Editor ed(&dst->fPathRef);
1384 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001386 /*
1387 * If we're not in perspective, we can transform all of the points at
1388 * once.
1389 *
1390 * Here we also want to optimize bounds, by noting if the bounds are
1391 * already known, and if so, we just transform those as well and mark
1392 * them as "known", rather than force the transformed path to have to
1393 * recompute them.
1394 *
1395 * Special gotchas if the path is effectively empty (<= 1 point) or
1396 * if it is non-finite. In those cases bounds need to stay empty,
1397 * regardless of the matrix.
1398 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001399 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001400 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001401 if (fIsFinite) {
1402 matrix.mapRect(&dst->fBounds, fBounds);
1403 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1404 dst->fBounds.setEmpty();
1405 }
1406 } else {
1407 dst->fIsFinite = false;
1408 dst->fBounds.setEmpty();
1409 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001411 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001412 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413 }
1414
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001415 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1416
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001419 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001420 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001422
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001423 // It's an oval only if it stays a rect.
1424 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001425
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 SkDEBUGCODE(dst->validate();)
1427 }
1428}
1429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430///////////////////////////////////////////////////////////////////////////////
1431///////////////////////////////////////////////////////////////////////////////
1432
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001433enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001434 kEmptyContour_SegmentState, // The current contour is empty. We may be
1435 // starting processing or we may have just
1436 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001437 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1438 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1439 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440};
1441
1442SkPath::Iter::Iter() {
1443#ifdef SK_DEBUG
1444 fPts = NULL;
1445 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001446 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001447 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448#endif
1449 // need to init enough to make next() harmlessly return kDone_Verb
1450 fVerbs = NULL;
1451 fVerbStop = NULL;
1452 fNeedClose = false;
1453}
1454
1455SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1456 this->setPath(path, forceClose);
1457}
1458
1459void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001460 fPts = path.fPathRef->points();
1461 fVerbs = path.fPathRef->verbs();
1462 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001463 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001464 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 fForceClose = SkToU8(forceClose);
1466 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001467 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468}
1469
1470bool SkPath::Iter::isClosedContour() const {
1471 if (fVerbs == NULL || fVerbs == fVerbStop) {
1472 return false;
1473 }
1474 if (fForceClose) {
1475 return true;
1476 }
1477
1478 const uint8_t* verbs = fVerbs;
1479 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001480
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001481 if (kMove_Verb == *(verbs - 1)) {
1482 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 }
1484
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001485 while (verbs > stop) {
1486 // verbs points one beyond the current verb, decrement first.
1487 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 if (kMove_Verb == v) {
1489 break;
1490 }
1491 if (kClose_Verb == v) {
1492 return true;
1493 }
1494 }
1495 return false;
1496}
1497
1498SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001499 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001501 // A special case: if both points are NaN, SkPoint::operation== returns
1502 // false, but the iterator expects that they are treated as the same.
1503 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001504 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1505 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001506 return kClose_Verb;
1507 }
1508
reed@google.com9e25dbf2012-05-15 17:05:38 +00001509 pts[0] = fLastPt;
1510 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 fLastPt = fMoveTo;
1512 fCloseLine = true;
1513 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001514 } else {
1515 pts[0] = fMoveTo;
1516 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518}
1519
reed@google.com9e25dbf2012-05-15 17:05:38 +00001520const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001521 if (fSegmentState == kAfterMove_SegmentState) {
1522 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001523 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001524 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001526 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1527 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001528 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530}
1531
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001532void SkPath::Iter::consumeDegenerateSegments() {
1533 // We need to step over anything that will not move the current draw point
1534 // forward before the next move is seen
1535 const uint8_t* lastMoveVerb = 0;
1536 const SkPoint* lastMovePt = 0;
1537 SkPoint lastPt = fLastPt;
1538 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001540 switch (verb) {
1541 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001542 // Keep a record of this most recent move
1543 lastMoveVerb = fVerbs;
1544 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001545 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001546 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001547 fPts++;
1548 break;
1549
1550 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001551 // A close when we are in a segment is always valid except when it
1552 // follows a move which follows a segment.
1553 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001554 return;
1555 }
1556 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001558 break;
1559
1560 case kLine_Verb:
1561 if (!IsLineDegenerate(lastPt, fPts[0])) {
1562 if (lastMoveVerb) {
1563 fVerbs = lastMoveVerb;
1564 fPts = lastMovePt;
1565 return;
1566 }
1567 return;
1568 }
1569 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001571 fPts++;
1572 break;
1573
1574 case kQuad_Verb:
1575 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1576 if (lastMoveVerb) {
1577 fVerbs = lastMoveVerb;
1578 fPts = lastMovePt;
1579 return;
1580 }
1581 return;
1582 }
1583 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001584 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001585 fPts += 2;
1586 break;
1587
1588 case kCubic_Verb:
1589 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1590 if (lastMoveVerb) {
1591 fVerbs = lastMoveVerb;
1592 fPts = lastMovePt;
1593 return;
1594 }
1595 return;
1596 }
1597 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001598 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001599 fPts += 3;
1600 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001601
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001602 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001603 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001604 }
1605 }
1606}
1607
reed@google.com4a3b7142012-05-16 17:16:46 +00001608SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001609 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001612 // Close the curve if requested and if there is some curve to close
1613 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001614 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 return kLine_Verb;
1616 }
1617 fNeedClose = false;
1618 return kClose_Verb;
1619 }
1620 return kDone_Verb;
1621 }
1622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001623 // fVerbs is one beyond the current verb, decrement first
1624 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001625 const SkPoint* SK_RESTRICT srcPts = fPts;
1626 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627
1628 switch (verb) {
1629 case kMove_Verb:
1630 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 verb = this->autoClose(pts);
1633 if (verb == kClose_Verb) {
1634 fNeedClose = false;
1635 }
1636 return (Verb)verb;
1637 }
1638 if (fVerbs == fVerbStop) { // might be a trailing moveto
1639 return kDone_Verb;
1640 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001641 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001642 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001644 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001645 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 fNeedClose = fForceClose;
1647 break;
1648 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001649 pts[0] = this->cons_moveTo();
1650 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 fLastPt = srcPts[0];
1652 fCloseLine = false;
1653 srcPts += 1;
1654 break;
1655 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001656 pts[0] = this->cons_moveTo();
1657 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 fLastPt = srcPts[1];
1659 srcPts += 2;
1660 break;
1661 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001662 pts[0] = this->cons_moveTo();
1663 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 fLastPt = srcPts[2];
1665 srcPts += 3;
1666 break;
1667 case kClose_Verb:
1668 verb = this->autoClose(pts);
1669 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001670 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 } else {
1672 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001673 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001675 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 break;
1677 }
1678 fPts = srcPts;
1679 return (Verb)verb;
1680}
1681
1682///////////////////////////////////////////////////////////////////////////////
1683
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001684SkPath::RawIter::RawIter() {
1685#ifdef SK_DEBUG
1686 fPts = NULL;
1687 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1688#endif
1689 // need to init enough to make next() harmlessly return kDone_Verb
1690 fVerbs = NULL;
1691 fVerbStop = NULL;
1692}
1693
1694SkPath::RawIter::RawIter(const SkPath& path) {
1695 this->setPath(path);
1696}
1697
1698void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001699 fPts = path.fPathRef->points();
1700 fVerbs = path.fPathRef->verbs();
1701 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001702 fMoveTo.fX = fMoveTo.fY = 0;
1703 fLastPt.fX = fLastPt.fY = 0;
1704}
1705
1706SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001707 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001708 if (fVerbs == fVerbStop) {
1709 return kDone_Verb;
1710 }
1711
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001712 // fVerbs points one beyond next verb so decrement first.
1713 unsigned verb = *(--fVerbs);
1714 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001715
1716 switch (verb) {
1717 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001718 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001719 fMoveTo = srcPts[0];
1720 fLastPt = fMoveTo;
1721 srcPts += 1;
1722 break;
1723 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001724 pts[0] = fLastPt;
1725 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001726 fLastPt = srcPts[0];
1727 srcPts += 1;
1728 break;
1729 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001730 pts[0] = fLastPt;
1731 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001732 fLastPt = srcPts[1];
1733 srcPts += 2;
1734 break;
1735 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001736 pts[0] = fLastPt;
1737 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001738 fLastPt = srcPts[2];
1739 srcPts += 3;
1740 break;
1741 case kClose_Verb:
1742 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001743 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001744 break;
1745 }
1746 fPts = srcPts;
1747 return (Verb)verb;
1748}
1749
1750///////////////////////////////////////////////////////////////////////////////
1751
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001753 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754*/
1755
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001756uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757 SkDEBUGCODE(this->validate();)
1758
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001759 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 const int byteCount = sizeof(int32_t)
1761#if NEW_PICTURE_FORMAT
1762 + fPathRef->writeSize()
1763#else
1764 + 2 * sizeof(int32_t)
1765 + sizeof(SkPoint) * fPathRef->countPoints()
1766 + sizeof(uint8_t) * fPathRef->countVerbs()
1767#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001768 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001769 return SkAlign4(byteCount);
1770 }
1771
1772 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773#if !NEW_PICTURE_FORMAT
1774 buffer.write32(fPathRef->countPoints());
1775 buffer.write32(fPathRef->countVerbs());
1776#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001777
1778 // Call getBounds() to ensure (as a side-effect) that fBounds
1779 // and fIsFinite are computed.
1780 const SkRect& bounds = this->getBounds();
1781 SkASSERT(!fBoundsIsDirty);
1782
1783 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
1784 ((fIsOval & 1) << kIsOval_SerializationShift) |
1785 (fConvexity << kConvexity_SerializationShift) |
1786 (fFillType << kFillType_SerializationShift) |
1787 (fSegmentMask << kSegmentMask_SerializationShift);
1788
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001789 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001791 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001792
1793 buffer.write(&bounds, sizeof(bounds));
1794
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001795 buffer.padToAlign4();
1796 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797}
1798
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001799uint32_t SkPath::readFromMemory(const void* storage) {
1800 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801#if !NEW_PICTURE_FORMAT
1802 int32_t pcount = buffer.readS32();
1803 int32_t vcount = buffer.readS32();
1804#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001805
reed@google.com98b11f12011-09-21 18:40:27 +00001806 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001807 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
1808 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
1809 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1810 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
1811 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
1812
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813#if NEW_PICTURE_FORMAT
1814 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
1815#else
1816 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
1817#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001818
1819 buffer.read(&fBounds, sizeof(fBounds));
1820 fBoundsIsDirty = false;
1821
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001822 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001823
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001824 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825
1826 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001827 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828}
1829
1830///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832void SkPath::dump(bool forceClose, const char title[]) const {
1833 Iter iter(*this, forceClose);
1834 SkPoint pts[4];
1835 Verb verb;
1836
1837 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1838 title ? title : "");
1839
reed@google.com4a3b7142012-05-16 17:16:46 +00001840 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 switch (verb) {
1842 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 SkDebugf(" path: moveTo [%g %g]\n",
1844 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845 break;
1846 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 SkDebugf(" path: lineTo [%g %g]\n",
1848 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 break;
1850 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1852 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1853 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 break;
1855 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1857 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1858 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1859 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 break;
1861 case kClose_Verb:
1862 SkDebugf(" path: close\n");
1863 break;
1864 default:
1865 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1866 verb = kDone_Verb; // stop the loop
1867 break;
1868 }
1869 }
1870 SkDebugf("path: done %s\n", title ? title : "");
1871}
1872
reed@android.come522ca52009-11-23 20:10:41 +00001873void SkPath::dump() const {
1874 this->dump(false);
1875}
1876
1877#ifdef SK_DEBUG
1878void SkPath::validate() const {
1879 SkASSERT(this != NULL);
1880 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00001881
reed@android.come522ca52009-11-23 20:10:41 +00001882 if (!fBoundsIsDirty) {
1883 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001884
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001886 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001887
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001888 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00001889 // if we're empty, fBounds may be empty but translated, so we can't
1890 // necessarily compare to bounds directly
1891 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1892 // be [2, 2, 2, 2]
1893 SkASSERT(bounds.isEmpty());
1894 SkASSERT(fBounds.isEmpty());
1895 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001896 if (bounds.isEmpty()) {
1897 SkASSERT(fBounds.isEmpty());
1898 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001899 if (!fBounds.isEmpty()) {
1900 SkASSERT(fBounds.contains(bounds));
1901 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001902 }
reed@android.come522ca52009-11-23 20:10:41 +00001903 }
1904 }
reed@google.com10296cc2011-09-21 12:29:05 +00001905
1906 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001907 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
1908 for (int i = 0; i < fPathRef->countVerbs(); i++) {
1909 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00001910 case kLine_Verb:
1911 mask |= kLine_SegmentMask;
1912 break;
1913 case kQuad_Verb:
1914 mask |= kQuad_SegmentMask;
1915 break;
1916 case kCubic_Verb:
1917 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 case kMove_Verb: // these verbs aren't included in the segment mask.
1919 case kClose_Verb:
1920 break;
1921 case kDone_Verb:
1922 SkDEBUGFAIL("Done verb shouldn't be recorded.");
1923 break;
1924 default:
1925 SkDEBUGFAIL("Unknown Verb");
1926 break;
reed@google.com10296cc2011-09-21 12:29:05 +00001927 }
1928 }
1929 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001930}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931#endif
reed@android.come522ca52009-11-23 20:10:41 +00001932
reed@google.com04863fa2011-05-15 04:08:24 +00001933///////////////////////////////////////////////////////////////////////////////
1934
reed@google.com0b7b9822011-05-16 12:29:27 +00001935static int sign(SkScalar x) { return x < 0; }
1936#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001937
reed@google.com04863fa2011-05-15 04:08:24 +00001938static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001939 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001940}
1941
1942// only valid for a single contour
1943struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001944 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001945 fSign = 0;
1946 // warnings
1947 fCurrPt.set(0, 0);
1948 fVec0.set(0, 0);
1949 fVec1.set(0, 0);
1950 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001951
1952 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001953 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001954 }
1955
1956 SkPath::Convexity getConvexity() const { return fConvexity; }
1957
1958 void addPt(const SkPoint& pt) {
1959 if (SkPath::kConcave_Convexity == fConvexity) {
1960 return;
1961 }
1962
1963 if (0 == fPtCount) {
1964 fCurrPt = pt;
1965 ++fPtCount;
1966 } else {
1967 SkVector vec = pt - fCurrPt;
1968 if (vec.fX || vec.fY) {
1969 fCurrPt = pt;
1970 if (++fPtCount == 2) {
1971 fFirstVec = fVec1 = vec;
1972 } else {
1973 SkASSERT(fPtCount > 2);
1974 this->addVec(vec);
1975 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001976
reed@google.com85b6e392011-05-15 20:25:17 +00001977 int sx = sign(vec.fX);
1978 int sy = sign(vec.fY);
1979 fDx += (sx != fSx);
1980 fDy += (sy != fSy);
1981 fSx = sx;
1982 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001983
reed@google.com85b6e392011-05-15 20:25:17 +00001984 if (fDx > 3 || fDy > 3) {
1985 fConvexity = SkPath::kConcave_Convexity;
1986 }
reed@google.com04863fa2011-05-15 04:08:24 +00001987 }
1988 }
1989 }
1990
1991 void close() {
1992 if (fPtCount > 2) {
1993 this->addVec(fFirstVec);
1994 }
1995 }
1996
1997private:
1998 void addVec(const SkVector& vec) {
1999 SkASSERT(vec.fX || vec.fY);
2000 fVec0 = fVec1;
2001 fVec1 = vec;
2002 int sign = CrossProductSign(fVec0, fVec1);
2003 if (0 == fSign) {
2004 fSign = sign;
2005 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002006 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002007 fConvexity = SkPath::kConcave_Convexity;
2008 }
2009 }
2010 }
2011
2012 SkPoint fCurrPt;
2013 SkVector fVec0, fVec1, fFirstVec;
2014 int fPtCount; // non-degenerate points
2015 int fSign;
2016 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00002017 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002018};
2019
2020SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
2021 SkPoint pts[4];
2022 SkPath::Verb verb;
2023 SkPath::Iter iter(path, true);
2024
2025 int contourCount = 0;
2026 int count;
2027 Convexicator state;
2028
2029 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2030 switch (verb) {
2031 case kMove_Verb:
2032 if (++contourCount > 1) {
2033 return kConcave_Convexity;
2034 }
2035 pts[1] = pts[0];
2036 count = 1;
2037 break;
2038 case kLine_Verb: count = 1; break;
2039 case kQuad_Verb: count = 2; break;
2040 case kCubic_Verb: count = 3; break;
2041 case kClose_Verb:
2042 state.close();
2043 count = 0;
2044 break;
2045 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002046 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00002047 return kConcave_Convexity;
2048 }
2049
2050 for (int i = 1; i <= count; i++) {
2051 state.addPt(pts[i]);
2052 }
2053 // early exit
2054 if (kConcave_Convexity == state.getConvexity()) {
2055 return kConcave_Convexity;
2056 }
2057 }
2058 return state.getConvexity();
2059}
reed@google.com69a99432012-01-10 18:00:10 +00002060
2061///////////////////////////////////////////////////////////////////////////////
2062
2063class ContourIter {
2064public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002065 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002066
2067 bool done() const { return fDone; }
2068 // if !done() then these may be called
2069 int count() const { return fCurrPtCount; }
2070 const SkPoint* pts() const { return fCurrPt; }
2071 void next();
2072
2073private:
2074 int fCurrPtCount;
2075 const SkPoint* fCurrPt;
2076 const uint8_t* fCurrVerb;
2077 const uint8_t* fStopVerbs;
2078 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002079 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002080};
2081
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002082ContourIter::ContourIter(const SkPathRef& pathRef) {
2083 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002084 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002085 fCurrPt = pathRef.points();
2086 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002087 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002088 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002089 this->next();
2090}
2091
2092void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002093 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002094 fDone = true;
2095 }
2096 if (fDone) {
2097 return;
2098 }
2099
2100 // skip pts of prev contour
2101 fCurrPt += fCurrPtCount;
2102
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002103 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002104 int ptCount = 1; // moveTo
2105 const uint8_t* verbs = fCurrVerb;
2106
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002107 for (--verbs; verbs > fStopVerbs; --verbs) {
2108 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002109 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002110 goto CONTOUR_END;
2111 case SkPath::kLine_Verb:
2112 ptCount += 1;
2113 break;
2114 case SkPath::kQuad_Verb:
2115 ptCount += 2;
2116 break;
2117 case SkPath::kCubic_Verb:
2118 ptCount += 3;
2119 break;
2120 default: // kClose_Verb, just keep going
2121 break;
2122 }
2123 }
2124CONTOUR_END:
2125 fCurrPtCount = ptCount;
2126 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002127 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002128}
2129
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002130// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002131static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002132 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2133 // We may get 0 when the above subtracts underflow. We expect this to be
2134 // very rare and lazily promote to double.
2135 if (0 == cross) {
2136 double p0x = SkScalarToDouble(p0.fX);
2137 double p0y = SkScalarToDouble(p0.fY);
2138
2139 double p1x = SkScalarToDouble(p1.fX);
2140 double p1y = SkScalarToDouble(p1.fY);
2141
2142 double p2x = SkScalarToDouble(p2.fX);
2143 double p2y = SkScalarToDouble(p2.fY);
2144
2145 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2146 (p1y - p0y) * (p2x - p0x));
2147
2148 }
2149 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002150}
2151
reed@google.comc1ea60a2012-01-31 15:15:36 +00002152// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002153static int find_max_y(const SkPoint pts[], int count) {
2154 SkASSERT(count > 0);
2155 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002156 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002157 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002158 SkScalar y = pts[i].fY;
2159 if (y > max) {
2160 max = y;
2161 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002162 }
2163 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002164 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002165}
2166
reed@google.comcabaf1d2012-01-11 21:03:05 +00002167static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2168 int i = index;
2169 for (;;) {
2170 i = (i + inc) % n;
2171 if (i == index) { // we wrapped around, so abort
2172 break;
2173 }
2174 if (pts[index] != pts[i]) { // found a different point, success!
2175 break;
2176 }
2177 }
2178 return i;
2179}
2180
reed@google.comc1ea60a2012-01-31 15:15:36 +00002181/**
2182 * Starting at index, and moving forward (incrementing), find the xmin and
2183 * xmax of the contiguous points that have the same Y.
2184 */
2185static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2186 int* maxIndexPtr) {
2187 const SkScalar y = pts[index].fY;
2188 SkScalar min = pts[index].fX;
2189 SkScalar max = min;
2190 int minIndex = index;
2191 int maxIndex = index;
2192 for (int i = index + 1; i < n; ++i) {
2193 if (pts[i].fY != y) {
2194 break;
2195 }
2196 SkScalar x = pts[i].fX;
2197 if (x < min) {
2198 min = x;
2199 minIndex = i;
2200 } else if (x > max) {
2201 max = x;
2202 maxIndex = i;
2203 }
2204 }
2205 *maxIndexPtr = maxIndex;
2206 return minIndex;
2207}
2208
reed@google.comac8543f2012-01-30 20:51:25 +00002209static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2210 if (dir) {
2211 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2212 }
2213 return true;
2214}
2215
reed@google.comc1ea60a2012-01-31 15:15:36 +00002216#if 0
2217#include "SkString.h"
2218#include "../utils/SkParsePath.h"
2219static void dumpPath(const SkPath& path) {
2220 SkString str;
2221 SkParsePath::ToSVGString(path, &str);
2222 SkDebugf("%s\n", str.c_str());
2223}
2224#endif
2225
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002226namespace {
2227// for use with convex_dir_test
2228double mul(double a, double b) { return a * b; }
2229SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2230double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2231SkScalar toScalar(SkScalar a) { return a; }
2232
2233// determines the winding direction of a convex polygon with the precision
2234// of T. CAST_SCALAR casts an SkScalar to T.
2235template <typename T, T (CAST_SCALAR)(SkScalar)>
2236bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2237 // we find the first three points that form a non-degenerate
2238 // triangle. If there are no such points then the path is
2239 // degenerate. The first is always point 0. Now we find the second
2240 // point.
2241 int i = 0;
2242 enum { kX = 0, kY = 1 };
2243 T v0[2];
2244 while (1) {
2245 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2246 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2247 if (v0[kX] || v0[kY]) {
2248 break;
2249 }
2250 if (++i == n - 1) {
2251 return false;
2252 }
2253 }
2254 // now find a third point that is not colinear with the first two
2255 // points and check the orientation of the triangle (which will be
2256 // the same as the orientation of the path).
2257 for (++i; i < n; ++i) {
2258 T v1[2];
2259 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2260 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2261 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2262 if (0 != cross) {
2263 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2264 return true;
2265 }
2266 }
2267 return false;
2268}
2269}
2270
reed@google.comac8543f2012-01-30 20:51:25 +00002271/*
2272 * We loop through all contours, and keep the computed cross-product of the
2273 * contour that contained the global y-max. If we just look at the first
2274 * contour, we may find one that is wound the opposite way (correctly) since
2275 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2276 * that is outer most (or at least has the global y-max) before we can consider
2277 * its cross product.
2278 */
reed@google.com69a99432012-01-10 18:00:10 +00002279bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002280// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002281 // don't want to pay the cost for computing this if it
2282 // is unknown, so we don't call isConvex()
2283 const Convexity conv = this->getConvexityOrUnknown();
2284
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002285 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002286
reed@google.comac8543f2012-01-30 20:51:25 +00002287 // initialize with our logical y-min
2288 SkScalar ymax = this->getBounds().fTop;
2289 SkScalar ymaxCross = 0;
2290
reed@google.com69a99432012-01-10 18:00:10 +00002291 for (; !iter.done(); iter.next()) {
2292 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002293 if (n < 3) {
2294 continue;
2295 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002296
reed@google.comcabaf1d2012-01-11 21:03:05 +00002297 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002298 SkScalar cross = 0;
2299 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002300 // We try first at scalar precision, and then again at double
2301 // precision. This is because the vectors computed between distant
2302 // points may lose too much precision.
2303 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2304 return true;
2305 }
2306 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2307 return true;
2308 } else {
2309 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002310 }
2311 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002312 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002313 if (pts[index].fY < ymax) {
2314 continue;
2315 }
2316
reed@google.comc1ea60a2012-01-31 15:15:36 +00002317 // If there is more than 1 distinct point at the y-max, we take the
2318 // x-min and x-max of them and just subtract to compute the dir.
2319 if (pts[(index + 1) % n].fY == pts[index].fY) {
2320 int maxIndex;
2321 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002322 if (minIndex == maxIndex) {
2323 goto TRY_CROSSPROD;
2324 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002325 SkASSERT(pts[minIndex].fY == pts[index].fY);
2326 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2327 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2328 // we just subtract the indices, and let that auto-convert to
2329 // SkScalar, since we just want - or + to signal the direction.
2330 cross = minIndex - maxIndex;
2331 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002332 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002333 // Find a next and prev index to use for the cross-product test,
2334 // but we try to find pts that form non-zero vectors from pts[index]
2335 //
2336 // Its possible that we can't find two non-degenerate vectors, so
2337 // we have to guard our search (e.g. all the pts could be in the
2338 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002339
reed@google.comc1ea60a2012-01-31 15:15:36 +00002340 // we pass n - 1 instead of -1 so we don't foul up % operator by
2341 // passing it a negative LH argument.
2342 int prev = find_diff_pt(pts, index, n, n - 1);
2343 if (prev == index) {
2344 // completely degenerate, skip to next contour
2345 continue;
2346 }
2347 int next = find_diff_pt(pts, index, n, 1);
2348 SkASSERT(next != index);
2349 cross = cross_prod(pts[prev], pts[index], pts[next]);
2350 // if we get a zero, but the pts aren't on top of each other, then
2351 // we can just look at the direction
2352 if (0 == cross) {
2353 // construct the subtract so we get the correct Direction below
2354 cross = pts[index].fX - pts[next].fX;
2355 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002356 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002357
reed@google.comac8543f2012-01-30 20:51:25 +00002358 if (cross) {
2359 // record our best guess so far
2360 ymax = pts[index].fY;
2361 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002362 }
reed@google.com69a99432012-01-10 18:00:10 +00002363 }
2364 }
reed@google.com69a99432012-01-10 18:00:10 +00002365
reed@google.comac8543f2012-01-30 20:51:25 +00002366 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2367}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002368
2369///////////////////////////////////////////////////////////////////////////////
2370
2371static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2372 SkScalar D, SkScalar t) {
2373 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2374}
2375
2376static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2377 SkScalar t) {
2378 SkScalar A = c3 + 3*(c1 - c2) - c0;
2379 SkScalar B = 3*(c2 - c1 - c1 + c0);
2380 SkScalar C = 3*(c1 - c0);
2381 SkScalar D = c0;
2382 return eval_cubic_coeff(A, B, C, D, t);
2383}
2384
2385/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2386 t value such that cubic(t) = target
2387 */
2388static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2389 SkScalar target, SkScalar* t) {
2390 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2391 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002392
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002393 SkScalar D = c0 - target;
2394 SkScalar A = c3 + 3*(c1 - c2) - c0;
2395 SkScalar B = 3*(c2 - c1 - c1 + c0);
2396 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002397
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002398 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2399 SkScalar minT = 0;
2400 SkScalar maxT = SK_Scalar1;
2401 SkScalar mid;
2402 int i;
2403 for (i = 0; i < 16; i++) {
2404 mid = SkScalarAve(minT, maxT);
2405 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2406 if (delta < 0) {
2407 minT = mid;
2408 delta = -delta;
2409 } else {
2410 maxT = mid;
2411 }
2412 if (delta < TOLERANCE) {
2413 break;
2414 }
2415 }
2416 *t = mid;
2417 return true;
2418}
2419
2420template <size_t N> static void find_minmax(const SkPoint pts[],
2421 SkScalar* minPtr, SkScalar* maxPtr) {
2422 SkScalar min, max;
2423 min = max = pts[0].fX;
2424 for (size_t i = 1; i < N; ++i) {
2425 min = SkMinScalar(min, pts[i].fX);
2426 max = SkMaxScalar(max, pts[i].fX);
2427 }
2428 *minPtr = min;
2429 *maxPtr = max;
2430}
2431
2432static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2433 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002434
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002435 int dir = 1;
2436 if (pts[0].fY > pts[3].fY) {
2437 storage[0] = pts[3];
2438 storage[1] = pts[2];
2439 storage[2] = pts[1];
2440 storage[3] = pts[0];
2441 pts = storage;
2442 dir = -1;
2443 }
2444 if (y < pts[0].fY || y >= pts[3].fY) {
2445 return 0;
2446 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002447
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002448 // quickreject or quickaccept
2449 SkScalar min, max;
2450 find_minmax<4>(pts, &min, &max);
2451 if (x < min) {
2452 return 0;
2453 }
2454 if (x > max) {
2455 return dir;
2456 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002457
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002458 // compute the actual x(t) value
2459 SkScalar t, xt;
2460 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2461 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2462 } else {
2463 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2464 xt = y < mid ? pts[0].fX : pts[3].fX;
2465 }
2466 return xt < x ? dir : 0;
2467}
2468
2469static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2470 SkPoint dst[10];
2471 int n = SkChopCubicAtYExtrema(pts, dst);
2472 int w = 0;
2473 for (int i = 0; i <= n; ++i) {
2474 w += winding_mono_cubic(&dst[i * 3], x, y);
2475 }
2476 return w;
2477}
2478
2479static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2480 SkScalar y0 = pts[0].fY;
2481 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002482
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002483 int dir = 1;
2484 if (y0 > y2) {
2485 SkTSwap(y0, y2);
2486 dir = -1;
2487 }
2488 if (y < y0 || y >= y2) {
2489 return 0;
2490 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002491
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002492 // bounds check on X (not required. is it faster?)
2493#if 0
2494 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2495 return 0;
2496 }
2497#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002498
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002499 SkScalar roots[2];
2500 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2501 2 * (pts[1].fY - pts[0].fY),
2502 pts[0].fY - y,
2503 roots);
2504 SkASSERT(n <= 1);
2505 SkScalar xt;
2506 if (0 == n) {
2507 SkScalar mid = SkScalarAve(y0, y2);
2508 // Need [0] and [2] if dir == 1
2509 // and [2] and [0] if dir == -1
2510 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2511 } else {
2512 SkScalar t = roots[0];
2513 SkScalar C = pts[0].fX;
2514 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2515 SkScalar B = 2 * (pts[1].fX - C);
2516 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2517 }
2518 return xt < x ? dir : 0;
2519}
2520
2521static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2522 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2523 if (y0 == y1) {
2524 return true;
2525 }
2526 if (y0 < y1) {
2527 return y1 <= y2;
2528 } else {
2529 return y1 >= y2;
2530 }
2531}
2532
2533static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2534 SkPoint dst[5];
2535 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002536
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002537 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2538 n = SkChopQuadAtYExtrema(pts, dst);
2539 pts = dst;
2540 }
2541 int w = winding_mono_quad(pts, x, y);
2542 if (n > 0) {
2543 w += winding_mono_quad(&pts[2], x, y);
2544 }
2545 return w;
2546}
2547
2548static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2549 SkScalar x0 = pts[0].fX;
2550 SkScalar y0 = pts[0].fY;
2551 SkScalar x1 = pts[1].fX;
2552 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002553
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002554 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002555
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002556 int dir = 1;
2557 if (y0 > y1) {
2558 SkTSwap(y0, y1);
2559 dir = -1;
2560 }
2561 if (y < y0 || y >= y1) {
2562 return 0;
2563 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002564
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002565 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2566 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002567
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002568 if (SkScalarSignAsInt(cross) == dir) {
2569 dir = 0;
2570 }
2571 return dir;
2572}
2573
2574bool SkPath::contains(SkScalar x, SkScalar y) const {
2575 bool isInverse = this->isInverseFillType();
2576 if (this->isEmpty()) {
2577 return isInverse;
2578 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002579
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002580 const SkRect& bounds = this->getBounds();
2581 if (!bounds.contains(x, y)) {
2582 return isInverse;
2583 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002584
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002585 SkPath::Iter iter(*this, true);
2586 bool done = false;
2587 int w = 0;
2588 do {
2589 SkPoint pts[4];
2590 switch (iter.next(pts, false)) {
2591 case SkPath::kMove_Verb:
2592 case SkPath::kClose_Verb:
2593 break;
2594 case SkPath::kLine_Verb:
2595 w += winding_line(pts, x, y);
2596 break;
2597 case SkPath::kQuad_Verb:
2598 w += winding_quad(pts, x, y);
2599 break;
2600 case SkPath::kCubic_Verb:
2601 w += winding_cubic(pts, x, y);
2602 break;
2603 case SkPath::kDone_Verb:
2604 done = true;
2605 break;
2606 }
2607 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002608
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002609 switch (this->getFillType()) {
2610 case SkPath::kEvenOdd_FillType:
2611 case SkPath::kInverseEvenOdd_FillType:
2612 w &= 1;
2613 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002614 default:
2615 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002616 }
2617 return SkToBool(w);
2618}
2619