blob: eb0e485ab33bde164094edeb373c6320f746865b [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.comae09f2d2012-10-03 19:57:01 +000016
17////////////////////////////////////////////////////////////////////////////
18
19#if SK_DEBUG_PATH_REF
20
21SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
22
23SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
24: fPathRef(pr)
25, fOwner(owner) {
26 pr->addOwner(owner);
27}
28
29SkPath::PathRefDebugRef::~PathRefDebugRef() {
30 fPathRef->removeOwner(fOwner);
31}
32
33void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
34 bool diff = (ref != fPathRef.get());
35 if (diff && NULL != fPathRef.get()) {
36 fPathRef.get()->removeOwner(fOwner);
37 }
38 fPathRef.reset(ref);
39 if (diff && NULL != fPathRef.get()) {
40 fPathRef.get()->addOwner(fOwner);
41 }
42}
43
44void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
45 if (other->fPathRef.get() != fPathRef.get()) {
46 other->fPathRef->removeOwner(other->fOwner);
47 other->fPathRef->addOwner(fOwner);
48
49 fPathRef->removeOwner(fOwner);
50 fPathRef->addOwner(other->fOwner);
51 }
52
53 fPathRef.swap(&other->fPathRef);
54}
55
56SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
57
58SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
59 return fPathRef.operator->();
60}
61
62SkPath::PathRefDebugRef::operator SkPathRef*() {
63 return fPathRef.operator SkPathRef *();
64}
65
66#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000067
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000068////////////////////////////////////////////////////////////////////////////
69
70
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000071SK_DEFINE_INST_COUNT(SkPath);
72
reed@google.com744faba2012-05-29 19:54:52 +000073// This value is just made-up for now. When count is 4, calling memset was much
74// slower than just writing the loop. This seems odd, and hopefully in the
75// future this we appear to have been a fluke...
76#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
77
reed@android.com8a1c16f2008-12-17 15:59:43 +000078////////////////////////////////////////////////////////////////////////////
79
reed@google.com3563c9e2011-11-14 19:34:57 +000080/**
81 * Path.bounds is defined to be the bounds of all the control points.
82 * If we called bounds.join(r) we would skip r if r was empty, which breaks
83 * our promise. Hence we have a custom joiner that doesn't look at emptiness
84 */
85static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
86 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
87 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
88 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
89 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
90}
91
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000092static bool is_degenerate(const SkPath& path) {
93 SkPath::Iter iter(path, false);
94 SkPoint pts[4];
95 return SkPath::kDone_Verb == iter.next(pts);
96}
97
bsalomon@google.com6aa29652012-04-18 13:29:52 +000098class SkAutoDisableOvalCheck {
99public:
100 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
101 fSaved = fPath->fIsOval;
102 }
103
104 ~SkAutoDisableOvalCheck() {
105 fPath->fIsOval = fSaved;
106 }
107
108private:
109 SkPath* fPath;
110 bool fSaved;
111};
112
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113/* This guy's constructor/destructor bracket a path editing operation. It is
114 used when we know the bounds of the amount we are going to add to the path
115 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000116
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 It captures some state about the path up front (i.e. if it already has a
118 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000119 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000120
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000121 It also notes if the path was originally degenerate, and if so, sets
122 isConvex to true. Thus it can only be used if the contour being added is
123 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124 */
125class SkAutoPathBoundsUpdate {
126public:
127 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
128 this->init(path);
129 }
130
131 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
132 SkScalar right, SkScalar bottom) {
133 fRect.set(left, top, right, bottom);
134 this->init(path);
135 }
reed@google.comabf15c12011-01-18 20:35:51 +0000136
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000138 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000140 fPath->fBounds = fRect;
141 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000142 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000144 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000145 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000146 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 }
148 }
reed@google.comabf15c12011-01-18 20:35:51 +0000149
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000151 SkPath* fPath;
152 SkRect fRect;
153 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000154 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000156
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000158 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000160 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000161 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000163 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000164 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165 }
166};
167
reed@google.com0bb18bb2012-07-26 15:20:36 +0000168// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000169static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
170 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000171 if (count <= 1) { // we ignore just 1 point (moveto)
172 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000173 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000175 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 }
177}
178
179////////////////////////////////////////////////////////////////////////////
180
181/*
182 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000183 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
185
186 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000187 1. If we encounter degenerate segments, remove them
188 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
189 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
190 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191*/
192
193////////////////////////////////////////////////////////////////////////////
194
reed@google.comd335d1d2012-01-12 18:17:11 +0000195// flag to require a moveTo if we begin with something else, like lineTo etc.
196#define INITIAL_LASTMOVETOINDEX_VALUE ~0
197
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000198SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000199#if SK_DEBUG_PATH_REF
200 : fPathRef(SkPathRef::CreateEmpty(), this)
201#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000202 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000203#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000204 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000205 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000206 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000207 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000208 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000209 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000210 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000211#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000212 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000213 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000214#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000215}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000217SkPath::SkPath(const SkPath& src)
218#if SK_DEBUG_PATH_REF
219 : fPathRef(this)
220#endif
221{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000223 src.fPathRef.get()->ref();
224 fPathRef.reset(src.fPathRef.get());
225 fBounds = src.fBounds;
226 fFillType = src.fFillType;
227 fBoundsIsDirty = src.fBoundsIsDirty;
228 fConvexity = src.fConvexity;
229 fIsFinite = src.fIsFinite;
230 fSegmentMask = src.fSegmentMask;
231 fLastMoveToIndex = src.fLastMoveToIndex;
232 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000233#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000234 fGenerationID = src.fGenerationID;
235 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000236#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237}
238
239SkPath::~SkPath() {
240 SkDEBUGCODE(this->validate();)
241}
242
243SkPath& SkPath::operator=(const SkPath& src) {
244 SkDEBUGCODE(src.validate();)
245
246 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000247 src.fPathRef.get()->ref();
248 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000249 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000250 fFillType = src.fFillType;
251 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000252 fConvexity = src.fConvexity;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000253 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000254 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000255 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000256 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000257 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 }
259 SkDEBUGCODE(this->validate();)
260 return *this;
261}
262
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000263SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000264 // note: don't need to look at isConvex or bounds, since just comparing the
265 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000266
267 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
268 // since it is only a cache of info in the fVerbs, but its a fast way to
269 // notice a difference
270
reed@android.com3abec1d2009-03-02 05:36:20 +0000271 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000272 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000273 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000274}
275
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276void SkPath::swap(SkPath& other) {
277 SkASSERT(&other != NULL);
278
279 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000280 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000281 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000283 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000284 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000285 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000286 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000287 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000288 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000289 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 }
291}
292
djsollen@google.com56c69772011-11-08 19:00:26 +0000293#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000294uint32_t SkPath::getGenerationID() const {
295 return fGenerationID;
296}
djsollen@google.come63793a2012-03-21 15:39:03 +0000297
298const SkPath* SkPath::getSourcePath() const {
299 return fSourcePath;
300}
301
302void SkPath::setSourcePath(const SkPath* path) {
303 fSourcePath = path;
304}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000305#endif
306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307void SkPath::reset() {
308 SkDEBUGCODE(this->validate();)
309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000310 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000311 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000312 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000313 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000314 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000315 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000316 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317}
318
319void SkPath::rewind() {
320 SkDEBUGCODE(this->validate();)
321
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000322 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000323 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000324 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000325 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000326 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000327 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000328 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329}
330
331bool SkPath::isEmpty() const {
332 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000333 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334}
335
reed@google.com7e6c4d12012-05-10 14:05:43 +0000336bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 int verbCount = fPathRef->countVerbs();
338 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000339
reed@google.com7e6c4d12012-05-10 14:05:43 +0000340 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000341 if (kMove_Verb == fPathRef->atVerb(0) &&
342 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000343 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000344 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000345 line[0] = pts[0];
346 line[1] = pts[1];
347 }
348 return true;
349 }
350 }
351 return false;
352}
353
caryclark@google.comf1316942011-07-26 19:54:45 +0000354/*
355 Determines if path is a rect by keeping track of changes in direction
356 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000357
caryclark@google.comf1316942011-07-26 19:54:45 +0000358 The direction is computed such that:
359 0: vertical up
360 1: horizontal right
361 2: vertical down
362 3: horizontal left
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364A rectangle cycles up/right/down/left or up/left/down/right.
365
366The test fails if:
367 The path is closed, and followed by a line.
368 A second move creates a new endpoint.
369 A diagonal line is parsed.
370 There's more than four changes of direction.
371 There's a discontinuity on the line (e.g., a move in the middle)
372 The line reverses direction.
373 The rectangle doesn't complete a cycle.
374 The path contains a quadratic or cubic.
375 The path contains fewer than four points.
376 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378It's OK if the path has:
379 Several colinear line segments composing a rectangle side.
380 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382The direction takes advantage of the corners found since opposite sides
383must travel in opposite directions.
384
385FIXME: Allow colinear quads and cubics to be treated like lines.
386FIXME: If the API passes fill-only, return true if the filled stroke
387 is a rectangle, though the caller failed to close the path.
388 */
389bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000390 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000391
caryclark@google.comf1316942011-07-26 19:54:45 +0000392 int corners = 0;
393 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000394 first.set(0, 0);
395 last.set(0, 0);
396 int firstDirection = 0;
397 int lastDirection = 0;
398 int nextDirection = 0;
399 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000400 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000401 const SkPoint* pts = fPathRef->points();
402 int verbCnt = fPathRef->countVerbs();
403 int currVerb = 0;
404 while (currVerb < verbCnt) {
405 switch (fPathRef->atVerb(currVerb++)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 case kClose_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000407 pts = fPathRef->points();
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 autoClose = true;
409 case kLine_Verb: {
410 SkScalar left = last.fX;
411 SkScalar top = last.fY;
412 SkScalar right = pts->fX;
413 SkScalar bottom = pts->fY;
414 ++pts;
415 if (left != right && top != bottom) {
416 return false; // diagonal
417 }
418 if (left == right && top == bottom) {
419 break; // single point on side OK
420 }
421 nextDirection = (left != right) << 0 |
422 (left < right || top < bottom) << 1;
423 if (0 == corners) {
424 firstDirection = nextDirection;
425 first = last;
426 last = pts[-1];
427 corners = 1;
428 closedOrMoved = false;
429 break;
430 }
431 if (closedOrMoved) {
432 return false; // closed followed by a line
433 }
434 closedOrMoved = autoClose;
435 if (lastDirection != nextDirection) {
436 if (++corners > 4) {
437 return false; // too many direction changes
438 }
439 }
440 last = pts[-1];
441 if (lastDirection == nextDirection) {
442 break; // colinear segment
443 }
444 // Possible values for corners are 2, 3, and 4.
445 // When corners == 3, nextDirection opposes firstDirection.
446 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000447 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
449 if ((directionCycle ^ turn) != nextDirection) {
450 return false; // direction didn't follow cycle
451 }
452 break;
453 }
454 case kQuad_Verb:
455 case kCubic_Verb:
456 return false; // quadratic, cubic not allowed
457 case kMove_Verb:
458 last = *pts++;
459 closedOrMoved = true;
460 break;
461 }
462 lastDirection = nextDirection;
463 }
464 // Success if 4 corners and first point equals last
465 bool result = 4 == corners && first == last;
466 if (result && rect) {
467 *rect = getBounds();
468 }
469 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470}
471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000472int SkPath::countPoints() const {
473 return fPathRef->countPoints();
474}
475
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000476int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477 SkDEBUGCODE(this->validate();)
478
479 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000480 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000481 int count = SkMin32(max, fPathRef->countPoints());
482 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
483 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484}
485
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000486SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000487 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
488 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000489 }
490 return SkPoint::Make(0, 0);
491}
492
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000493int SkPath::countVerbs() const {
494 return fPathRef->countVerbs();
495}
496
497static inline void copy_verbs_reverse(uint8_t* inorderDst,
498 const uint8_t* reversedSrc,
499 int count) {
500 for (int i = 0; i < count; ++i) {
501 inorderDst[i] = reversedSrc[~i];
502 }
503}
504
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000505int SkPath::getVerbs(uint8_t dst[], int max) const {
506 SkDEBUGCODE(this->validate();)
507
508 SkASSERT(max >= 0);
509 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000510 int count = SkMin32(max, fPathRef->countVerbs());
511 copy_verbs_reverse(dst, fPathRef->verbs(), count);
512 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000513}
514
reed@google.com294dd7b2011-10-11 11:58:32 +0000515bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516 SkDEBUGCODE(this->validate();)
517
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000518 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000519 if (count > 0) {
520 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000521 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000523 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000524 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000525 if (lastPt) {
526 lastPt->set(0, 0);
527 }
528 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000529}
530
531void SkPath::setLastPt(SkScalar x, SkScalar y) {
532 SkDEBUGCODE(this->validate();)
533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000534 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 if (count == 0) {
536 this->moveTo(x, y);
537 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000538 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000539 SkPathRef::Editor ed(&fPathRef);
540 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000541 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000542 }
543}
544
reed@android.comd252db02009-04-01 18:31:44 +0000545void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000547 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000548
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000549 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000550 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551}
552
reed@google.com04863fa2011-05-15 04:08:24 +0000553void SkPath::setConvexity(Convexity c) {
554 if (fConvexity != c) {
555 fConvexity = c;
556 GEN_ID_INC;
557 }
558}
559
reed@android.com8a1c16f2008-12-17 15:59:43 +0000560//////////////////////////////////////////////////////////////////////////////
561// Construction methods
562
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000563#define DIRTY_AFTER_EDIT \
564 do { \
565 fBoundsIsDirty = true; \
566 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000567 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000568 } while (0)
569
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000570#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
571 do { \
572 fBoundsIsDirty = true; \
573 } while (0)
574
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575void SkPath::incReserve(U16CPU inc) {
576 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000577 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 SkDEBUGCODE(this->validate();)
579}
580
581void SkPath::moveTo(SkScalar x, SkScalar y) {
582 SkDEBUGCODE(this->validate();)
583
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585
reed@google.comd335d1d2012-01-12 18:17:11 +0000586 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000587 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000588
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000589 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000591 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000592 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593}
594
595void SkPath::rMoveTo(SkScalar x, SkScalar y) {
596 SkPoint pt;
597 this->getLastPt(&pt);
598 this->moveTo(pt.fX + x, pt.fY + y);
599}
600
reed@google.comd335d1d2012-01-12 18:17:11 +0000601void SkPath::injectMoveToIfNeeded() {
602 if (fLastMoveToIndex < 0) {
603 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000604 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000605 x = y = 0;
606 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000607 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000608 x = pt.fX;
609 y = pt.fY;
610 }
611 this->moveTo(x, y);
612 }
613}
614
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615void SkPath::lineTo(SkScalar x, SkScalar y) {
616 SkDEBUGCODE(this->validate();)
617
reed@google.comd335d1d2012-01-12 18:17:11 +0000618 this->injectMoveToIfNeeded();
619
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000620 SkPathRef::Editor ed(&fPathRef);
621 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000622 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000624 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000625 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626}
627
628void SkPath::rLineTo(SkScalar x, SkScalar y) {
629 SkPoint pt;
630 this->getLastPt(&pt);
631 this->lineTo(pt.fX + x, pt.fY + y);
632}
633
634void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
635 SkDEBUGCODE(this->validate();)
636
reed@google.comd335d1d2012-01-12 18:17:11 +0000637 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 SkPathRef::Editor ed(&fPathRef);
640 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 pts[0].set(x1, y1);
642 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000643 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000645 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000646 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647}
648
649void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
650 SkPoint pt;
651 this->getLastPt(&pt);
652 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
653}
654
655void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
656 SkScalar x3, SkScalar y3) {
657 SkDEBUGCODE(this->validate();)
658
reed@google.comd335d1d2012-01-12 18:17:11 +0000659 this->injectMoveToIfNeeded();
660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor ed(&fPathRef);
662 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 pts[0].set(x1, y1);
664 pts[1].set(x2, y2);
665 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000666 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000668 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000669 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670}
671
672void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
673 SkScalar x3, SkScalar y3) {
674 SkPoint pt;
675 this->getLastPt(&pt);
676 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
677 pt.fX + x3, pt.fY + y3);
678}
679
680void SkPath::close() {
681 SkDEBUGCODE(this->validate();)
682
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 case kLine_Verb:
687 case kQuad_Verb:
688 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 case kMove_Verb: {
690 SkPathRef::Editor ed(&fPathRef);
691 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000692 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000696 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 break;
698 }
699 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000700
701 // signal that we need a moveTo to follow us (unless we're done)
702#if 0
703 if (fLastMoveToIndex >= 0) {
704 fLastMoveToIndex = ~fLastMoveToIndex;
705 }
706#else
707 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
708#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709}
710
711///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713void SkPath::addRect(const SkRect& rect, Direction dir) {
714 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
715}
716
717void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
718 SkScalar bottom, Direction dir) {
719 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
720
721 this->incReserve(5);
722
723 this->moveTo(left, top);
724 if (dir == kCCW_Direction) {
725 this->lineTo(left, bottom);
726 this->lineTo(right, bottom);
727 this->lineTo(right, top);
728 } else {
729 this->lineTo(right, top);
730 this->lineTo(right, bottom);
731 this->lineTo(left, bottom);
732 }
733 this->close();
734}
735
reed@google.com744faba2012-05-29 19:54:52 +0000736void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
737 SkDEBUGCODE(this->validate();)
738 if (count <= 0) {
739 return;
740 }
741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000742 SkPathRef::Editor ed(&fPathRef);
743 fLastMoveToIndex = ed.pathRef()->countPoints();
744 uint8_t* vb;
745 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000746 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000748
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 memcpy(p, pts, count * sizeof(SkPoint));
750 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000751 if (count > 1) {
752 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
753 // be 0, the compiler will remove the test/branch entirely.
754 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000756 } else {
757 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000758 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000759 }
760 }
761 fSegmentMask |= kLine_SegmentMask;
762 }
763 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000764 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000765 }
766
767 GEN_ID_INC;
768 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000770}
771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
773
774void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
775 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 SkScalar w = rect.width();
777 SkScalar halfW = SkScalarHalf(w);
778 SkScalar h = rect.height();
779 SkScalar halfH = SkScalarHalf(h);
780
781 if (halfW <= 0 || halfH <= 0) {
782 return;
783 }
784
reed@google.comabf15c12011-01-18 20:35:51 +0000785 bool skip_hori = rx >= halfW;
786 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787
788 if (skip_hori && skip_vert) {
789 this->addOval(rect, dir);
790 return;
791 }
reed@google.comabf15c12011-01-18 20:35:51 +0000792
793 SkAutoPathBoundsUpdate apbu(this, rect);
794
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 if (skip_hori) {
796 rx = halfW;
797 } else if (skip_vert) {
798 ry = halfH;
799 }
800
801 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
802 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
803
804 this->incReserve(17);
805 this->moveTo(rect.fRight - rx, rect.fTop);
806 if (dir == kCCW_Direction) {
807 if (!skip_hori) {
808 this->lineTo(rect.fLeft + rx, rect.fTop); // top
809 }
810 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
811 rect.fLeft, rect.fTop + ry - sy,
812 rect.fLeft, rect.fTop + ry); // top-left
813 if (!skip_vert) {
814 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
815 }
816 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
817 rect.fLeft + rx - sx, rect.fBottom,
818 rect.fLeft + rx, rect.fBottom); // bot-left
819 if (!skip_hori) {
820 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
821 }
822 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
823 rect.fRight, rect.fBottom - ry + sy,
824 rect.fRight, rect.fBottom - ry); // bot-right
825 if (!skip_vert) {
826 this->lineTo(rect.fRight, rect.fTop + ry);
827 }
828 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
829 rect.fRight - rx + sx, rect.fTop,
830 rect.fRight - rx, rect.fTop); // top-right
831 } else {
832 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
833 rect.fRight, rect.fTop + ry - sy,
834 rect.fRight, rect.fTop + ry); // top-right
835 if (!skip_vert) {
836 this->lineTo(rect.fRight, rect.fBottom - ry);
837 }
838 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
839 rect.fRight - rx + sx, rect.fBottom,
840 rect.fRight - rx, rect.fBottom); // bot-right
841 if (!skip_hori) {
842 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
843 }
844 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
845 rect.fLeft, rect.fBottom - ry + sy,
846 rect.fLeft, rect.fBottom - ry); // bot-left
847 if (!skip_vert) {
848 this->lineTo(rect.fLeft, rect.fTop + ry); // left
849 }
850 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
851 rect.fLeft + rx - sx, rect.fTop,
852 rect.fLeft + rx, rect.fTop); // top-left
853 if (!skip_hori) {
854 this->lineTo(rect.fRight - rx, rect.fTop); // top
855 }
856 }
857 this->close();
858}
859
860static void add_corner_arc(SkPath* path, const SkRect& rect,
861 SkScalar rx, SkScalar ry, int startAngle,
862 SkPath::Direction dir, bool forceMoveTo) {
863 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
864 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000865
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866 SkRect r;
867 r.set(-rx, -ry, rx, ry);
868
869 switch (startAngle) {
870 case 0:
871 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
872 break;
873 case 90:
874 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
875 break;
876 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
877 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000878 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 }
reed@google.comabf15c12011-01-18 20:35:51 +0000880
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 SkScalar start = SkIntToScalar(startAngle);
882 SkScalar sweep = SkIntToScalar(90);
883 if (SkPath::kCCW_Direction == dir) {
884 start += sweep;
885 sweep = -sweep;
886 }
reed@google.comabf15c12011-01-18 20:35:51 +0000887
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 path->arcTo(r, start, sweep, forceMoveTo);
889}
890
891void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
892 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000893 // abort before we invoke SkAutoPathBoundsUpdate()
894 if (rect.isEmpty()) {
895 return;
896 }
897
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898 SkAutoPathBoundsUpdate apbu(this, rect);
899
900 if (kCW_Direction == dir) {
901 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
902 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
903 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
904 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
905 } else {
906 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
907 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
908 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
909 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
910 }
911 this->close();
912}
913
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000914bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000915 int count = fPathRef->countVerbs();
916 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
917 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000918 if (*verbs == kLine_Verb ||
919 *verbs == kQuad_Verb ||
920 *verbs == kCubic_Verb) {
921 return false;
922 }
923 ++verbs;
924 }
925 return true;
926}
927
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000929 /* If addOval() is called after previous moveTo(),
930 this path is still marked as an oval. This is used to
931 fit into WebKit's calling sequences.
932 We can't simply check isEmpty() in this case, as additional
933 moveTo() would mark the path non empty.
934 */
935 fIsOval = hasOnlyMoveTos();
936
937 SkAutoDisableOvalCheck adoc(this);
938
reed@android.com8a1c16f2008-12-17 15:59:43 +0000939 SkAutoPathBoundsUpdate apbu(this, oval);
940
941 SkScalar cx = oval.centerX();
942 SkScalar cy = oval.centerY();
943 SkScalar rx = SkScalarHalf(oval.width());
944 SkScalar ry = SkScalarHalf(oval.height());
945#if 0 // these seem faster than using quads (1/2 the number of edges)
946 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
947 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
948
949 this->incReserve(13);
950 this->moveTo(cx + rx, cy);
951 if (dir == kCCW_Direction) {
952 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
953 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
954 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
955 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
956 } else {
957 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
958 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
959 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
960 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
961 }
962#else
963 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
964 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
965 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
966 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
967
968 /*
969 To handle imprecision in computing the center and radii, we revert to
970 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
971 to ensure that we don't exceed the oval's bounds *ever*, since we want
972 to use oval for our fast-bounds, rather than have to recompute it.
973 */
974 const SkScalar L = oval.fLeft; // cx - rx
975 const SkScalar T = oval.fTop; // cy - ry
976 const SkScalar R = oval.fRight; // cx + rx
977 const SkScalar B = oval.fBottom; // cy + ry
978
979 this->incReserve(17); // 8 quads + close
980 this->moveTo(R, cy);
981 if (dir == kCCW_Direction) {
982 this->quadTo( R, cy - sy, cx + mx, cy - my);
983 this->quadTo(cx + sx, T, cx , T);
984 this->quadTo(cx - sx, T, cx - mx, cy - my);
985 this->quadTo( L, cy - sy, L, cy );
986 this->quadTo( L, cy + sy, cx - mx, cy + my);
987 this->quadTo(cx - sx, B, cx , B);
988 this->quadTo(cx + sx, B, cx + mx, cy + my);
989 this->quadTo( R, cy + sy, R, cy );
990 } else {
991 this->quadTo( R, cy + sy, cx + mx, cy + my);
992 this->quadTo(cx + sx, B, cx , B);
993 this->quadTo(cx - sx, B, cx - mx, cy + my);
994 this->quadTo( L, cy + sy, L, cy );
995 this->quadTo( L, cy - sy, cx - mx, cy - my);
996 this->quadTo(cx - sx, T, cx , T);
997 this->quadTo(cx + sx, T, cx + mx, cy - my);
998 this->quadTo( R, cy - sy, R, cy );
999 }
1000#endif
1001 this->close();
1002}
1003
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001004bool SkPath::isOval(SkRect* rect) const {
1005 if (fIsOval && rect) {
1006 *rect = getBounds();
1007 }
1008
1009 return fIsOval;
1010}
1011
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1013 if (r > 0) {
1014 SkRect rect;
1015 rect.set(x - r, y - r, x + r, y + r);
1016 this->addOval(rect, dir);
1017 }
1018}
1019
1020#include "SkGeometry.h"
1021
1022static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1023 SkScalar sweepAngle,
1024 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001025
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001026 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001027 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1028 // Chrome uses this path to move into and out of ovals. If not
1029 // treated as a special case the moves can distort the oval's
1030 // bounding box (and break the circle special case).
1031 pts[0].set(oval.fRight, oval.centerY());
1032 return 1;
1033 }
1034
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 SkVector start, stop;
1036
1037 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1038 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1039 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001040
1041 /* If the sweep angle is nearly (but less than) 360, then due to precision
1042 loss in radians-conversion and/or sin/cos, we may end up with coincident
1043 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1044 of drawing a nearly complete circle (good).
1045 e.g. canvas.drawArc(0, 359.99, ...)
1046 -vs- canvas.drawArc(0, 359.9, ...)
1047 We try to detect this edge case, and tweak the stop vector
1048 */
1049 if (start == stop) {
1050 SkScalar sw = SkScalarAbs(sweepAngle);
1051 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1052 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1053 // make a guess at a tiny angle (in radians) to tweak by
1054 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1055 // not sure how much will be enough, so we use a loop
1056 do {
1057 stopRad -= deltaRad;
1058 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1059 } while (start == stop);
1060 }
1061 }
1062
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001064
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1066 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001067
reed@android.com8a1c16f2008-12-17 15:59:43 +00001068 return SkBuildQuadArc(start, stop,
1069 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1070 &matrix, pts);
1071}
1072
1073void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1074 bool forceMoveTo) {
1075 if (oval.width() < 0 || oval.height() < 0) {
1076 return;
1077 }
1078
1079 SkPoint pts[kSkBuildQuadArcStorage];
1080 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1081 SkASSERT((count & 1) == 1);
1082
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001083 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 forceMoveTo = true;
1085 }
1086 this->incReserve(count);
1087 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1088 for (int i = 1; i < count; i += 2) {
1089 this->quadTo(pts[i], pts[i+1]);
1090 }
1091}
1092
1093void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1094 SkScalar sweepAngle) {
1095 if (oval.isEmpty() || 0 == sweepAngle) {
1096 return;
1097 }
1098
1099 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1100
1101 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1102 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1103 return;
1104 }
1105
1106 SkPoint pts[kSkBuildQuadArcStorage];
1107 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1108
1109 this->incReserve(count);
1110 this->moveTo(pts[0]);
1111 for (int i = 1; i < count; i += 2) {
1112 this->quadTo(pts[i], pts[i+1]);
1113 }
1114}
1115
1116/*
1117 Need to handle the case when the angle is sharp, and our computed end-points
1118 for the arc go behind pt1 and/or p2...
1119*/
1120void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1121 SkScalar radius) {
1122 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001123
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 // need to know our prev pt so we can construct tangent vectors
1125 {
1126 SkPoint start;
1127 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001128 // Handle degenerate cases by adding a line to the first point and
1129 // bailing out.
1130 if ((x1 == start.fX && y1 == start.fY) ||
1131 (x1 == x2 && y1 == y2) ||
1132 radius == 0) {
1133 this->lineTo(x1, y1);
1134 return;
1135 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001136 before.setNormalize(x1 - start.fX, y1 - start.fY);
1137 after.setNormalize(x2 - x1, y2 - y1);
1138 }
reed@google.comabf15c12011-01-18 20:35:51 +00001139
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140 SkScalar cosh = SkPoint::DotProduct(before, after);
1141 SkScalar sinh = SkPoint::CrossProduct(before, after);
1142
1143 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001144 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001145 return;
1146 }
reed@google.comabf15c12011-01-18 20:35:51 +00001147
reed@android.com8a1c16f2008-12-17 15:59:43 +00001148 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1149 if (dist < 0) {
1150 dist = -dist;
1151 }
1152
1153 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1154 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1155 SkRotationDirection arcDir;
1156
1157 // now turn before/after into normals
1158 if (sinh > 0) {
1159 before.rotateCCW();
1160 after.rotateCCW();
1161 arcDir = kCW_SkRotationDirection;
1162 } else {
1163 before.rotateCW();
1164 after.rotateCW();
1165 arcDir = kCCW_SkRotationDirection;
1166 }
1167
1168 SkMatrix matrix;
1169 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001170
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 matrix.setScale(radius, radius);
1172 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1173 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001174
reed@android.com8a1c16f2008-12-17 15:59:43 +00001175 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001176
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177 this->incReserve(count);
1178 // [xx,yy] == pts[0]
1179 this->lineTo(xx, yy);
1180 for (int i = 1; i < count; i += 2) {
1181 this->quadTo(pts[i], pts[i+1]);
1182 }
1183}
1184
1185///////////////////////////////////////////////////////////////////////////////
1186
1187void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1188 SkMatrix matrix;
1189
1190 matrix.setTranslate(dx, dy);
1191 this->addPath(path, matrix);
1192}
1193
1194void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001195 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001196
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197 fIsOval = false;
1198
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001199 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200 SkPoint pts[4];
1201 Verb verb;
1202
1203 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1204
1205 while ((verb = iter.next(pts)) != kDone_Verb) {
1206 switch (verb) {
1207 case kMove_Verb:
1208 proc(matrix, &pts[0], &pts[0], 1);
1209 this->moveTo(pts[0]);
1210 break;
1211 case kLine_Verb:
1212 proc(matrix, &pts[1], &pts[1], 1);
1213 this->lineTo(pts[1]);
1214 break;
1215 case kQuad_Verb:
1216 proc(matrix, &pts[1], &pts[1], 2);
1217 this->quadTo(pts[1], pts[2]);
1218 break;
1219 case kCubic_Verb:
1220 proc(matrix, &pts[1], &pts[1], 3);
1221 this->cubicTo(pts[1], pts[2], pts[3]);
1222 break;
1223 case kClose_Verb:
1224 this->close();
1225 break;
1226 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001227 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 }
1229 }
1230}
1231
1232///////////////////////////////////////////////////////////////////////////////
1233
1234static const uint8_t gPtsInVerb[] = {
1235 1, // kMove
1236 1, // kLine
1237 2, // kQuad
1238 3, // kCubic
1239 0, // kClose
1240 0 // kDone
1241};
1242
1243// ignore the initial moveto, and stop when the 1st contour ends
1244void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001245 int i, vcount = path.fPathRef->countVerbs();
1246 // exit early if the path is empty, or just has a moveTo.
1247 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 return;
1249 }
1250
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001251 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253 fIsOval = false;
1254
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001255 const uint8_t* verbs = path.fPathRef->verbs();
1256 // skip the initial moveTo
1257 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001259 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001261 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 case kLine_Verb:
1263 this->lineTo(pts[0].fX, pts[0].fY);
1264 break;
1265 case kQuad_Verb:
1266 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1267 break;
1268 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001269 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 +00001270 break;
1271 case kClose_Verb:
1272 return;
1273 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001274 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 }
1276}
1277
1278// ignore the last point of the 1st contour
1279void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001280 int i, vcount = path.fPathRef->countVerbs();
1281 // exit early if the path is empty, or just has a moveTo.
1282 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 return;
1284 }
1285
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001286 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001288 fIsOval = false;
1289
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001290 const uint8_t* verbs = path.fPathRef->verbs();
1291 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001293 SkASSERT(verbs[~0] == kMove_Verb);
1294 for (i = 1; i < vcount; ++i) {
1295 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 if (n == 0) {
1297 break;
1298 }
1299 pts += n;
1300 }
1301
1302 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001303 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 case kLine_Verb:
1305 this->lineTo(pts[-1].fX, pts[-1].fY);
1306 break;
1307 case kQuad_Verb:
1308 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1309 break;
1310 case kCubic_Verb:
1311 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1312 pts[-3].fX, pts[-3].fY);
1313 break;
1314 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001315 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316 break;
1317 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001318 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 }
1320}
1321
reed@google.com63d73742012-01-10 15:33:12 +00001322void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001323 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001325 const SkPoint* pts = src.fPathRef->pointsEnd();
1326 // we will iterator through src's verbs backwards
1327 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1328 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001329
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001330 fIsOval = false;
1331
reed@google.com63d73742012-01-10 15:33:12 +00001332 bool needMove = true;
1333 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001334 while (verbs < verbsEnd) {
1335 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001336 int n = gPtsInVerb[v];
1337
1338 if (needMove) {
1339 --pts;
1340 this->moveTo(pts->fX, pts->fY);
1341 needMove = false;
1342 }
1343 pts -= n;
1344 switch (v) {
1345 case kMove_Verb:
1346 if (needClose) {
1347 this->close();
1348 needClose = false;
1349 }
1350 needMove = true;
1351 pts += 1; // so we see the point in "if (needMove)" above
1352 break;
1353 case kLine_Verb:
1354 this->lineTo(pts[0]);
1355 break;
1356 case kQuad_Verb:
1357 this->quadTo(pts[1], pts[0]);
1358 break;
1359 case kCubic_Verb:
1360 this->cubicTo(pts[2], pts[1], pts[0]);
1361 break;
1362 case kClose_Verb:
1363 needClose = true;
1364 break;
1365 default:
1366 SkASSERT(!"unexpected verb");
1367 }
1368 }
1369}
1370
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371///////////////////////////////////////////////////////////////////////////////
1372
1373void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1374 SkMatrix matrix;
1375
1376 matrix.setTranslate(dx, dy);
1377 this->transform(matrix, dst);
1378}
1379
1380#include "SkGeometry.h"
1381
1382static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1383 int level = 2) {
1384 if (--level >= 0) {
1385 SkPoint tmp[5];
1386
1387 SkChopQuadAtHalf(pts, tmp);
1388 subdivide_quad_to(path, &tmp[0], level);
1389 subdivide_quad_to(path, &tmp[2], level);
1390 } else {
1391 path->quadTo(pts[1], pts[2]);
1392 }
1393}
1394
1395static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1396 int level = 2) {
1397 if (--level >= 0) {
1398 SkPoint tmp[7];
1399
1400 SkChopCubicAtHalf(pts, tmp);
1401 subdivide_cubic_to(path, &tmp[0], level);
1402 subdivide_cubic_to(path, &tmp[3], level);
1403 } else {
1404 path->cubicTo(pts[1], pts[2], pts[3]);
1405 }
1406}
1407
1408void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1409 SkDEBUGCODE(this->validate();)
1410 if (dst == NULL) {
1411 dst = (SkPath*)this;
1412 }
1413
tomhudson@google.com8d430182011-06-06 19:11:19 +00001414 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415 SkPath tmp;
1416 tmp.fFillType = fFillType;
1417
1418 SkPath::Iter iter(*this, false);
1419 SkPoint pts[4];
1420 SkPath::Verb verb;
1421
reed@google.com4a3b7142012-05-16 17:16:46 +00001422 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 switch (verb) {
1424 case kMove_Verb:
1425 tmp.moveTo(pts[0]);
1426 break;
1427 case kLine_Verb:
1428 tmp.lineTo(pts[1]);
1429 break;
1430 case kQuad_Verb:
1431 subdivide_quad_to(&tmp, pts);
1432 break;
1433 case kCubic_Verb:
1434 subdivide_cubic_to(&tmp, pts);
1435 break;
1436 case kClose_Verb:
1437 tmp.close();
1438 break;
1439 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001440 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 break;
1442 }
1443 }
1444
1445 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001446 SkPathRef::Editor ed(&dst->fPathRef);
1447 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001449 /*
1450 * If we're not in perspective, we can transform all of the points at
1451 * once.
1452 *
1453 * Here we also want to optimize bounds, by noting if the bounds are
1454 * already known, and if so, we just transform those as well and mark
1455 * them as "known", rather than force the transformed path to have to
1456 * recompute them.
1457 *
1458 * Special gotchas if the path is effectively empty (<= 1 point) or
1459 * if it is non-finite. In those cases bounds need to stay empty,
1460 * regardless of the matrix.
1461 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001463 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001464 if (fIsFinite) {
1465 matrix.mapRect(&dst->fBounds, fBounds);
1466 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1467 dst->fBounds.setEmpty();
1468 }
1469 } else {
1470 dst->fIsFinite = false;
1471 dst->fBounds.setEmpty();
1472 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001474 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001475 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 }
1477
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001478 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1479
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001482 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001483 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001486 // It's an oval only if it stays a rect.
1487 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001488
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 SkDEBUGCODE(dst->validate();)
1490 }
1491}
1492
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493///////////////////////////////////////////////////////////////////////////////
1494///////////////////////////////////////////////////////////////////////////////
1495
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001496enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001497 kEmptyContour_SegmentState, // The current contour is empty. We may be
1498 // starting processing or we may have just
1499 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001500 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1501 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1502 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503};
1504
1505SkPath::Iter::Iter() {
1506#ifdef SK_DEBUG
1507 fPts = NULL;
1508 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001509 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001510 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511#endif
1512 // need to init enough to make next() harmlessly return kDone_Verb
1513 fVerbs = NULL;
1514 fVerbStop = NULL;
1515 fNeedClose = false;
1516}
1517
1518SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1519 this->setPath(path, forceClose);
1520}
1521
1522void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 fPts = path.fPathRef->points();
1524 fVerbs = path.fPathRef->verbs();
1525 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001526 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001527 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 fForceClose = SkToU8(forceClose);
1529 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001530 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531}
1532
1533bool SkPath::Iter::isClosedContour() const {
1534 if (fVerbs == NULL || fVerbs == fVerbStop) {
1535 return false;
1536 }
1537 if (fForceClose) {
1538 return true;
1539 }
1540
1541 const uint8_t* verbs = fVerbs;
1542 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001543
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 if (kMove_Verb == *(verbs - 1)) {
1545 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 }
1547
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001548 while (verbs > stop) {
1549 // verbs points one beyond the current verb, decrement first.
1550 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 if (kMove_Verb == v) {
1552 break;
1553 }
1554 if (kClose_Verb == v) {
1555 return true;
1556 }
1557 }
1558 return false;
1559}
1560
1561SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001562 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001564 // A special case: if both points are NaN, SkPoint::operation== returns
1565 // false, but the iterator expects that they are treated as the same.
1566 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001567 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1568 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001569 return kClose_Verb;
1570 }
1571
reed@google.com9e25dbf2012-05-15 17:05:38 +00001572 pts[0] = fLastPt;
1573 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 fLastPt = fMoveTo;
1575 fCloseLine = true;
1576 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001577 } else {
1578 pts[0] = fMoveTo;
1579 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581}
1582
reed@google.com9e25dbf2012-05-15 17:05:38 +00001583const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001584 if (fSegmentState == kAfterMove_SegmentState) {
1585 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001586 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001587 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001589 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1590 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001591 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593}
1594
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001595void SkPath::Iter::consumeDegenerateSegments() {
1596 // We need to step over anything that will not move the current draw point
1597 // forward before the next move is seen
1598 const uint8_t* lastMoveVerb = 0;
1599 const SkPoint* lastMovePt = 0;
1600 SkPoint lastPt = fLastPt;
1601 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001602 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001603 switch (verb) {
1604 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605 // Keep a record of this most recent move
1606 lastMoveVerb = fVerbs;
1607 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001608 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001609 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 fPts++;
1611 break;
1612
1613 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001614 // A close when we are in a segment is always valid except when it
1615 // follows a move which follows a segment.
1616 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001617 return;
1618 }
1619 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001620 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001621 break;
1622
1623 case kLine_Verb:
1624 if (!IsLineDegenerate(lastPt, fPts[0])) {
1625 if (lastMoveVerb) {
1626 fVerbs = lastMoveVerb;
1627 fPts = lastMovePt;
1628 return;
1629 }
1630 return;
1631 }
1632 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001633 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001634 fPts++;
1635 break;
1636
1637 case kQuad_Verb:
1638 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1639 if (lastMoveVerb) {
1640 fVerbs = lastMoveVerb;
1641 fPts = lastMovePt;
1642 return;
1643 }
1644 return;
1645 }
1646 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001647 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001648 fPts += 2;
1649 break;
1650
1651 case kCubic_Verb:
1652 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1653 if (lastMoveVerb) {
1654 fVerbs = lastMoveVerb;
1655 fPts = lastMovePt;
1656 return;
1657 }
1658 return;
1659 }
1660 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 fPts += 3;
1663 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001664
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001665 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001666 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001667 }
1668 }
1669}
1670
reed@google.com4a3b7142012-05-16 17:16:46 +00001671SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001672 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001673
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001675 // Close the curve if requested and if there is some curve to close
1676 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001677 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 return kLine_Verb;
1679 }
1680 fNeedClose = false;
1681 return kClose_Verb;
1682 }
1683 return kDone_Verb;
1684 }
1685
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001686 // fVerbs is one beyond the current verb, decrement first
1687 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001688 const SkPoint* SK_RESTRICT srcPts = fPts;
1689 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690
1691 switch (verb) {
1692 case kMove_Verb:
1693 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001694 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 verb = this->autoClose(pts);
1696 if (verb == kClose_Verb) {
1697 fNeedClose = false;
1698 }
1699 return (Verb)verb;
1700 }
1701 if (fVerbs == fVerbStop) { // might be a trailing moveto
1702 return kDone_Verb;
1703 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001704 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001705 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001707 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709 fNeedClose = fForceClose;
1710 break;
1711 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001712 pts[0] = this->cons_moveTo();
1713 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 fLastPt = srcPts[0];
1715 fCloseLine = false;
1716 srcPts += 1;
1717 break;
1718 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001719 pts[0] = this->cons_moveTo();
1720 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 fLastPt = srcPts[1];
1722 srcPts += 2;
1723 break;
1724 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001725 pts[0] = this->cons_moveTo();
1726 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 fLastPt = srcPts[2];
1728 srcPts += 3;
1729 break;
1730 case kClose_Verb:
1731 verb = this->autoClose(pts);
1732 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 } else {
1735 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001736 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 break;
1740 }
1741 fPts = srcPts;
1742 return (Verb)verb;
1743}
1744
1745///////////////////////////////////////////////////////////////////////////////
1746
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001747SkPath::RawIter::RawIter() {
1748#ifdef SK_DEBUG
1749 fPts = NULL;
1750 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1751#endif
1752 // need to init enough to make next() harmlessly return kDone_Verb
1753 fVerbs = NULL;
1754 fVerbStop = NULL;
1755}
1756
1757SkPath::RawIter::RawIter(const SkPath& path) {
1758 this->setPath(path);
1759}
1760
1761void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001762 fPts = path.fPathRef->points();
1763 fVerbs = path.fPathRef->verbs();
1764 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001765 fMoveTo.fX = fMoveTo.fY = 0;
1766 fLastPt.fX = fLastPt.fY = 0;
1767}
1768
1769SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001770 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001771 if (fVerbs == fVerbStop) {
1772 return kDone_Verb;
1773 }
1774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 // fVerbs points one beyond next verb so decrement first.
1776 unsigned verb = *(--fVerbs);
1777 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001778
1779 switch (verb) {
1780 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001781 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001782 fMoveTo = srcPts[0];
1783 fLastPt = fMoveTo;
1784 srcPts += 1;
1785 break;
1786 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001787 pts[0] = fLastPt;
1788 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001789 fLastPt = srcPts[0];
1790 srcPts += 1;
1791 break;
1792 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001793 pts[0] = fLastPt;
1794 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001795 fLastPt = srcPts[1];
1796 srcPts += 2;
1797 break;
1798 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001799 pts[0] = fLastPt;
1800 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001801 fLastPt = srcPts[2];
1802 srcPts += 3;
1803 break;
1804 case kClose_Verb:
1805 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001806 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001807 break;
1808 }
1809 fPts = srcPts;
1810 return (Verb)verb;
1811}
1812
1813///////////////////////////////////////////////////////////////////////////////
1814
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001816 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817*/
1818
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001819uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 SkDEBUGCODE(this->validate();)
1821
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001822 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001823 const int byteCount = sizeof(int32_t)
1824#if NEW_PICTURE_FORMAT
1825 + fPathRef->writeSize()
1826#else
1827 + 2 * sizeof(int32_t)
1828 + sizeof(SkPoint) * fPathRef->countPoints()
1829 + sizeof(uint8_t) * fPathRef->countVerbs()
1830#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001831 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001832 return SkAlign4(byteCount);
1833 }
1834
1835 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836#if !NEW_PICTURE_FORMAT
1837 buffer.write32(fPathRef->countPoints());
1838 buffer.write32(fPathRef->countVerbs());
1839#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001840
1841 // Call getBounds() to ensure (as a side-effect) that fBounds
1842 // and fIsFinite are computed.
1843 const SkRect& bounds = this->getBounds();
1844 SkASSERT(!fBoundsIsDirty);
1845
1846 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
1847 ((fIsOval & 1) << kIsOval_SerializationShift) |
1848 (fConvexity << kConvexity_SerializationShift) |
1849 (fFillType << kFillType_SerializationShift) |
1850 (fSegmentMask << kSegmentMask_SerializationShift);
1851
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001852 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001853
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001854 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001855
1856 buffer.write(&bounds, sizeof(bounds));
1857
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001858 buffer.padToAlign4();
1859 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860}
1861
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001862uint32_t SkPath::readFromMemory(const void* storage) {
1863 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864#if !NEW_PICTURE_FORMAT
1865 int32_t pcount = buffer.readS32();
1866 int32_t vcount = buffer.readS32();
1867#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001868
reed@google.com98b11f12011-09-21 18:40:27 +00001869 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001870 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
1871 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
1872 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1873 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
1874 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
1875
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001876#if NEW_PICTURE_FORMAT
1877 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
1878#else
1879 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
1880#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001881
1882 buffer.read(&fBounds, sizeof(fBounds));
1883 fBoundsIsDirty = false;
1884
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001885 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001886
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001887 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888
1889 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001890 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891}
1892
1893///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895void SkPath::dump(bool forceClose, const char title[]) const {
1896 Iter iter(*this, forceClose);
1897 SkPoint pts[4];
1898 Verb verb;
1899
1900 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1901 title ? title : "");
1902
reed@google.com4a3b7142012-05-16 17:16:46 +00001903 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 switch (verb) {
1905 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 SkDebugf(" path: moveTo [%g %g]\n",
1907 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 break;
1909 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 SkDebugf(" path: lineTo [%g %g]\n",
1911 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 break;
1913 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1915 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1916 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 break;
1918 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1920 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1921 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1922 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 break;
1924 case kClose_Verb:
1925 SkDebugf(" path: close\n");
1926 break;
1927 default:
1928 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1929 verb = kDone_Verb; // stop the loop
1930 break;
1931 }
1932 }
1933 SkDebugf("path: done %s\n", title ? title : "");
1934}
1935
reed@android.come522ca52009-11-23 20:10:41 +00001936void SkPath::dump() const {
1937 this->dump(false);
1938}
1939
1940#ifdef SK_DEBUG
1941void SkPath::validate() const {
1942 SkASSERT(this != NULL);
1943 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00001944
reed@android.come522ca52009-11-23 20:10:41 +00001945 if (!fBoundsIsDirty) {
1946 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001947
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001948 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001949 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001950
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001951 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00001952 // if we're empty, fBounds may be empty but translated, so we can't
1953 // necessarily compare to bounds directly
1954 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1955 // be [2, 2, 2, 2]
1956 SkASSERT(bounds.isEmpty());
1957 SkASSERT(fBounds.isEmpty());
1958 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001959 if (bounds.isEmpty()) {
1960 SkASSERT(fBounds.isEmpty());
1961 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001962 if (!fBounds.isEmpty()) {
1963 SkASSERT(fBounds.contains(bounds));
1964 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001965 }
reed@android.come522ca52009-11-23 20:10:41 +00001966 }
1967 }
reed@google.com10296cc2011-09-21 12:29:05 +00001968
1969 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001970 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
1971 for (int i = 0; i < fPathRef->countVerbs(); i++) {
1972 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00001973 case kLine_Verb:
1974 mask |= kLine_SegmentMask;
1975 break;
1976 case kQuad_Verb:
1977 mask |= kQuad_SegmentMask;
1978 break;
1979 case kCubic_Verb:
1980 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001981 case kMove_Verb: // these verbs aren't included in the segment mask.
1982 case kClose_Verb:
1983 break;
1984 case kDone_Verb:
1985 SkDEBUGFAIL("Done verb shouldn't be recorded.");
1986 break;
1987 default:
1988 SkDEBUGFAIL("Unknown Verb");
1989 break;
reed@google.com10296cc2011-09-21 12:29:05 +00001990 }
1991 }
1992 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001993}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994#endif
reed@android.come522ca52009-11-23 20:10:41 +00001995
reed@google.com04863fa2011-05-15 04:08:24 +00001996///////////////////////////////////////////////////////////////////////////////
1997
reed@google.com0b7b9822011-05-16 12:29:27 +00001998static int sign(SkScalar x) { return x < 0; }
1999#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002000
reed@google.com04863fa2011-05-15 04:08:24 +00002001static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002002 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002003}
2004
2005// only valid for a single contour
2006struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00002007 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00002008 fSign = 0;
2009 // warnings
2010 fCurrPt.set(0, 0);
2011 fVec0.set(0, 0);
2012 fVec1.set(0, 0);
2013 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002014
2015 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002016 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002017 }
2018
2019 SkPath::Convexity getConvexity() const { return fConvexity; }
2020
2021 void addPt(const SkPoint& pt) {
2022 if (SkPath::kConcave_Convexity == fConvexity) {
2023 return;
2024 }
2025
2026 if (0 == fPtCount) {
2027 fCurrPt = pt;
2028 ++fPtCount;
2029 } else {
2030 SkVector vec = pt - fCurrPt;
2031 if (vec.fX || vec.fY) {
2032 fCurrPt = pt;
2033 if (++fPtCount == 2) {
2034 fFirstVec = fVec1 = vec;
2035 } else {
2036 SkASSERT(fPtCount > 2);
2037 this->addVec(vec);
2038 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002039
reed@google.com85b6e392011-05-15 20:25:17 +00002040 int sx = sign(vec.fX);
2041 int sy = sign(vec.fY);
2042 fDx += (sx != fSx);
2043 fDy += (sy != fSy);
2044 fSx = sx;
2045 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002046
reed@google.com85b6e392011-05-15 20:25:17 +00002047 if (fDx > 3 || fDy > 3) {
2048 fConvexity = SkPath::kConcave_Convexity;
2049 }
reed@google.com04863fa2011-05-15 04:08:24 +00002050 }
2051 }
2052 }
2053
2054 void close() {
2055 if (fPtCount > 2) {
2056 this->addVec(fFirstVec);
2057 }
2058 }
2059
2060private:
2061 void addVec(const SkVector& vec) {
2062 SkASSERT(vec.fX || vec.fY);
2063 fVec0 = fVec1;
2064 fVec1 = vec;
2065 int sign = CrossProductSign(fVec0, fVec1);
2066 if (0 == fSign) {
2067 fSign = sign;
2068 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002069 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002070 fConvexity = SkPath::kConcave_Convexity;
2071 }
2072 }
2073 }
2074
2075 SkPoint fCurrPt;
2076 SkVector fVec0, fVec1, fFirstVec;
2077 int fPtCount; // non-degenerate points
2078 int fSign;
2079 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00002080 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002081};
2082
2083SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
2084 SkPoint pts[4];
2085 SkPath::Verb verb;
2086 SkPath::Iter iter(path, true);
2087
2088 int contourCount = 0;
2089 int count;
2090 Convexicator state;
2091
2092 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2093 switch (verb) {
2094 case kMove_Verb:
2095 if (++contourCount > 1) {
2096 return kConcave_Convexity;
2097 }
2098 pts[1] = pts[0];
2099 count = 1;
2100 break;
2101 case kLine_Verb: count = 1; break;
2102 case kQuad_Verb: count = 2; break;
2103 case kCubic_Verb: count = 3; break;
2104 case kClose_Verb:
2105 state.close();
2106 count = 0;
2107 break;
2108 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002109 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00002110 return kConcave_Convexity;
2111 }
2112
2113 for (int i = 1; i <= count; i++) {
2114 state.addPt(pts[i]);
2115 }
2116 // early exit
2117 if (kConcave_Convexity == state.getConvexity()) {
2118 return kConcave_Convexity;
2119 }
2120 }
2121 return state.getConvexity();
2122}
reed@google.com69a99432012-01-10 18:00:10 +00002123
2124///////////////////////////////////////////////////////////////////////////////
2125
2126class ContourIter {
2127public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002128 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002129
2130 bool done() const { return fDone; }
2131 // if !done() then these may be called
2132 int count() const { return fCurrPtCount; }
2133 const SkPoint* pts() const { return fCurrPt; }
2134 void next();
2135
2136private:
2137 int fCurrPtCount;
2138 const SkPoint* fCurrPt;
2139 const uint8_t* fCurrVerb;
2140 const uint8_t* fStopVerbs;
2141 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002142 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002143};
2144
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002145ContourIter::ContourIter(const SkPathRef& pathRef) {
2146 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002147 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002148 fCurrPt = pathRef.points();
2149 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002150 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002151 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002152 this->next();
2153}
2154
2155void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002156 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002157 fDone = true;
2158 }
2159 if (fDone) {
2160 return;
2161 }
2162
2163 // skip pts of prev contour
2164 fCurrPt += fCurrPtCount;
2165
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002166 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002167 int ptCount = 1; // moveTo
2168 const uint8_t* verbs = fCurrVerb;
2169
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002170 for (--verbs; verbs > fStopVerbs; --verbs) {
2171 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002172 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002173 goto CONTOUR_END;
2174 case SkPath::kLine_Verb:
2175 ptCount += 1;
2176 break;
2177 case SkPath::kQuad_Verb:
2178 ptCount += 2;
2179 break;
2180 case SkPath::kCubic_Verb:
2181 ptCount += 3;
2182 break;
2183 default: // kClose_Verb, just keep going
2184 break;
2185 }
2186 }
2187CONTOUR_END:
2188 fCurrPtCount = ptCount;
2189 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002190 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002191}
2192
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002193// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002194static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002195 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2196 // We may get 0 when the above subtracts underflow. We expect this to be
2197 // very rare and lazily promote to double.
2198 if (0 == cross) {
2199 double p0x = SkScalarToDouble(p0.fX);
2200 double p0y = SkScalarToDouble(p0.fY);
2201
2202 double p1x = SkScalarToDouble(p1.fX);
2203 double p1y = SkScalarToDouble(p1.fY);
2204
2205 double p2x = SkScalarToDouble(p2.fX);
2206 double p2y = SkScalarToDouble(p2.fY);
2207
2208 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2209 (p1y - p0y) * (p2x - p0x));
2210
2211 }
2212 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002213}
2214
reed@google.comc1ea60a2012-01-31 15:15:36 +00002215// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002216static int find_max_y(const SkPoint pts[], int count) {
2217 SkASSERT(count > 0);
2218 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002219 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002220 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002221 SkScalar y = pts[i].fY;
2222 if (y > max) {
2223 max = y;
2224 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002225 }
2226 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002227 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002228}
2229
reed@google.comcabaf1d2012-01-11 21:03:05 +00002230static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2231 int i = index;
2232 for (;;) {
2233 i = (i + inc) % n;
2234 if (i == index) { // we wrapped around, so abort
2235 break;
2236 }
2237 if (pts[index] != pts[i]) { // found a different point, success!
2238 break;
2239 }
2240 }
2241 return i;
2242}
2243
reed@google.comc1ea60a2012-01-31 15:15:36 +00002244/**
2245 * Starting at index, and moving forward (incrementing), find the xmin and
2246 * xmax of the contiguous points that have the same Y.
2247 */
2248static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2249 int* maxIndexPtr) {
2250 const SkScalar y = pts[index].fY;
2251 SkScalar min = pts[index].fX;
2252 SkScalar max = min;
2253 int minIndex = index;
2254 int maxIndex = index;
2255 for (int i = index + 1; i < n; ++i) {
2256 if (pts[i].fY != y) {
2257 break;
2258 }
2259 SkScalar x = pts[i].fX;
2260 if (x < min) {
2261 min = x;
2262 minIndex = i;
2263 } else if (x > max) {
2264 max = x;
2265 maxIndex = i;
2266 }
2267 }
2268 *maxIndexPtr = maxIndex;
2269 return minIndex;
2270}
2271
reed@google.comac8543f2012-01-30 20:51:25 +00002272static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2273 if (dir) {
2274 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2275 }
2276 return true;
2277}
2278
reed@google.comc1ea60a2012-01-31 15:15:36 +00002279#if 0
2280#include "SkString.h"
2281#include "../utils/SkParsePath.h"
2282static void dumpPath(const SkPath& path) {
2283 SkString str;
2284 SkParsePath::ToSVGString(path, &str);
2285 SkDebugf("%s\n", str.c_str());
2286}
2287#endif
2288
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002289namespace {
2290// for use with convex_dir_test
2291double mul(double a, double b) { return a * b; }
2292SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2293double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2294SkScalar toScalar(SkScalar a) { return a; }
2295
2296// determines the winding direction of a convex polygon with the precision
2297// of T. CAST_SCALAR casts an SkScalar to T.
2298template <typename T, T (CAST_SCALAR)(SkScalar)>
2299bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2300 // we find the first three points that form a non-degenerate
2301 // triangle. If there are no such points then the path is
2302 // degenerate. The first is always point 0. Now we find the second
2303 // point.
2304 int i = 0;
2305 enum { kX = 0, kY = 1 };
2306 T v0[2];
2307 while (1) {
2308 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2309 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2310 if (v0[kX] || v0[kY]) {
2311 break;
2312 }
2313 if (++i == n - 1) {
2314 return false;
2315 }
2316 }
2317 // now find a third point that is not colinear with the first two
2318 // points and check the orientation of the triangle (which will be
2319 // the same as the orientation of the path).
2320 for (++i; i < n; ++i) {
2321 T v1[2];
2322 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2323 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2324 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2325 if (0 != cross) {
2326 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2327 return true;
2328 }
2329 }
2330 return false;
2331}
2332}
2333
reed@google.comac8543f2012-01-30 20:51:25 +00002334/*
2335 * We loop through all contours, and keep the computed cross-product of the
2336 * contour that contained the global y-max. If we just look at the first
2337 * contour, we may find one that is wound the opposite way (correctly) since
2338 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2339 * that is outer most (or at least has the global y-max) before we can consider
2340 * its cross product.
2341 */
reed@google.com69a99432012-01-10 18:00:10 +00002342bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002343// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002344 // don't want to pay the cost for computing this if it
2345 // is unknown, so we don't call isConvex()
2346 const Convexity conv = this->getConvexityOrUnknown();
2347
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002348 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002349
reed@google.comac8543f2012-01-30 20:51:25 +00002350 // initialize with our logical y-min
2351 SkScalar ymax = this->getBounds().fTop;
2352 SkScalar ymaxCross = 0;
2353
reed@google.com69a99432012-01-10 18:00:10 +00002354 for (; !iter.done(); iter.next()) {
2355 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002356 if (n < 3) {
2357 continue;
2358 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002359
reed@google.comcabaf1d2012-01-11 21:03:05 +00002360 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002361 SkScalar cross = 0;
2362 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002363 // We try first at scalar precision, and then again at double
2364 // precision. This is because the vectors computed between distant
2365 // points may lose too much precision.
2366 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2367 return true;
2368 }
2369 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2370 return true;
2371 } else {
2372 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002373 }
2374 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002375 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002376 if (pts[index].fY < ymax) {
2377 continue;
2378 }
2379
reed@google.comc1ea60a2012-01-31 15:15:36 +00002380 // If there is more than 1 distinct point at the y-max, we take the
2381 // x-min and x-max of them and just subtract to compute the dir.
2382 if (pts[(index + 1) % n].fY == pts[index].fY) {
2383 int maxIndex;
2384 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002385 if (minIndex == maxIndex) {
2386 goto TRY_CROSSPROD;
2387 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002388 SkASSERT(pts[minIndex].fY == pts[index].fY);
2389 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2390 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2391 // we just subtract the indices, and let that auto-convert to
2392 // SkScalar, since we just want - or + to signal the direction.
2393 cross = minIndex - maxIndex;
2394 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002395 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002396 // Find a next and prev index to use for the cross-product test,
2397 // but we try to find pts that form non-zero vectors from pts[index]
2398 //
2399 // Its possible that we can't find two non-degenerate vectors, so
2400 // we have to guard our search (e.g. all the pts could be in the
2401 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002402
reed@google.comc1ea60a2012-01-31 15:15:36 +00002403 // we pass n - 1 instead of -1 so we don't foul up % operator by
2404 // passing it a negative LH argument.
2405 int prev = find_diff_pt(pts, index, n, n - 1);
2406 if (prev == index) {
2407 // completely degenerate, skip to next contour
2408 continue;
2409 }
2410 int next = find_diff_pt(pts, index, n, 1);
2411 SkASSERT(next != index);
2412 cross = cross_prod(pts[prev], pts[index], pts[next]);
2413 // if we get a zero, but the pts aren't on top of each other, then
2414 // we can just look at the direction
2415 if (0 == cross) {
2416 // construct the subtract so we get the correct Direction below
2417 cross = pts[index].fX - pts[next].fX;
2418 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002419 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002420
reed@google.comac8543f2012-01-30 20:51:25 +00002421 if (cross) {
2422 // record our best guess so far
2423 ymax = pts[index].fY;
2424 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002425 }
reed@google.com69a99432012-01-10 18:00:10 +00002426 }
2427 }
reed@google.com69a99432012-01-10 18:00:10 +00002428
reed@google.comac8543f2012-01-30 20:51:25 +00002429 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2430}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002431
2432///////////////////////////////////////////////////////////////////////////////
2433
2434static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2435 SkScalar D, SkScalar t) {
2436 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2437}
2438
2439static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2440 SkScalar t) {
2441 SkScalar A = c3 + 3*(c1 - c2) - c0;
2442 SkScalar B = 3*(c2 - c1 - c1 + c0);
2443 SkScalar C = 3*(c1 - c0);
2444 SkScalar D = c0;
2445 return eval_cubic_coeff(A, B, C, D, t);
2446}
2447
2448/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2449 t value such that cubic(t) = target
2450 */
2451static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2452 SkScalar target, SkScalar* t) {
2453 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2454 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002455
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002456 SkScalar D = c0 - target;
2457 SkScalar A = c3 + 3*(c1 - c2) - c0;
2458 SkScalar B = 3*(c2 - c1 - c1 + c0);
2459 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002460
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002461 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2462 SkScalar minT = 0;
2463 SkScalar maxT = SK_Scalar1;
2464 SkScalar mid;
2465 int i;
2466 for (i = 0; i < 16; i++) {
2467 mid = SkScalarAve(minT, maxT);
2468 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2469 if (delta < 0) {
2470 minT = mid;
2471 delta = -delta;
2472 } else {
2473 maxT = mid;
2474 }
2475 if (delta < TOLERANCE) {
2476 break;
2477 }
2478 }
2479 *t = mid;
2480 return true;
2481}
2482
2483template <size_t N> static void find_minmax(const SkPoint pts[],
2484 SkScalar* minPtr, SkScalar* maxPtr) {
2485 SkScalar min, max;
2486 min = max = pts[0].fX;
2487 for (size_t i = 1; i < N; ++i) {
2488 min = SkMinScalar(min, pts[i].fX);
2489 max = SkMaxScalar(max, pts[i].fX);
2490 }
2491 *minPtr = min;
2492 *maxPtr = max;
2493}
2494
2495static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2496 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002497
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002498 int dir = 1;
2499 if (pts[0].fY > pts[3].fY) {
2500 storage[0] = pts[3];
2501 storage[1] = pts[2];
2502 storage[2] = pts[1];
2503 storage[3] = pts[0];
2504 pts = storage;
2505 dir = -1;
2506 }
2507 if (y < pts[0].fY || y >= pts[3].fY) {
2508 return 0;
2509 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002510
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002511 // quickreject or quickaccept
2512 SkScalar min, max;
2513 find_minmax<4>(pts, &min, &max);
2514 if (x < min) {
2515 return 0;
2516 }
2517 if (x > max) {
2518 return dir;
2519 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002520
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002521 // compute the actual x(t) value
2522 SkScalar t, xt;
2523 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2524 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2525 } else {
2526 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2527 xt = y < mid ? pts[0].fX : pts[3].fX;
2528 }
2529 return xt < x ? dir : 0;
2530}
2531
2532static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2533 SkPoint dst[10];
2534 int n = SkChopCubicAtYExtrema(pts, dst);
2535 int w = 0;
2536 for (int i = 0; i <= n; ++i) {
2537 w += winding_mono_cubic(&dst[i * 3], x, y);
2538 }
2539 return w;
2540}
2541
2542static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2543 SkScalar y0 = pts[0].fY;
2544 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002545
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002546 int dir = 1;
2547 if (y0 > y2) {
2548 SkTSwap(y0, y2);
2549 dir = -1;
2550 }
2551 if (y < y0 || y >= y2) {
2552 return 0;
2553 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002554
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002555 // bounds check on X (not required. is it faster?)
2556#if 0
2557 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2558 return 0;
2559 }
2560#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002561
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002562 SkScalar roots[2];
2563 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2564 2 * (pts[1].fY - pts[0].fY),
2565 pts[0].fY - y,
2566 roots);
2567 SkASSERT(n <= 1);
2568 SkScalar xt;
2569 if (0 == n) {
2570 SkScalar mid = SkScalarAve(y0, y2);
2571 // Need [0] and [2] if dir == 1
2572 // and [2] and [0] if dir == -1
2573 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2574 } else {
2575 SkScalar t = roots[0];
2576 SkScalar C = pts[0].fX;
2577 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2578 SkScalar B = 2 * (pts[1].fX - C);
2579 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2580 }
2581 return xt < x ? dir : 0;
2582}
2583
2584static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2585 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2586 if (y0 == y1) {
2587 return true;
2588 }
2589 if (y0 < y1) {
2590 return y1 <= y2;
2591 } else {
2592 return y1 >= y2;
2593 }
2594}
2595
2596static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2597 SkPoint dst[5];
2598 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002599
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002600 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2601 n = SkChopQuadAtYExtrema(pts, dst);
2602 pts = dst;
2603 }
2604 int w = winding_mono_quad(pts, x, y);
2605 if (n > 0) {
2606 w += winding_mono_quad(&pts[2], x, y);
2607 }
2608 return w;
2609}
2610
2611static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2612 SkScalar x0 = pts[0].fX;
2613 SkScalar y0 = pts[0].fY;
2614 SkScalar x1 = pts[1].fX;
2615 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002616
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002617 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002618
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002619 int dir = 1;
2620 if (y0 > y1) {
2621 SkTSwap(y0, y1);
2622 dir = -1;
2623 }
2624 if (y < y0 || y >= y1) {
2625 return 0;
2626 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002627
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002628 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2629 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002630
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002631 if (SkScalarSignAsInt(cross) == dir) {
2632 dir = 0;
2633 }
2634 return dir;
2635}
2636
2637bool SkPath::contains(SkScalar x, SkScalar y) const {
2638 bool isInverse = this->isInverseFillType();
2639 if (this->isEmpty()) {
2640 return isInverse;
2641 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002642
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002643 const SkRect& bounds = this->getBounds();
2644 if (!bounds.contains(x, y)) {
2645 return isInverse;
2646 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002647
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002648 SkPath::Iter iter(*this, true);
2649 bool done = false;
2650 int w = 0;
2651 do {
2652 SkPoint pts[4];
2653 switch (iter.next(pts, false)) {
2654 case SkPath::kMove_Verb:
2655 case SkPath::kClose_Verb:
2656 break;
2657 case SkPath::kLine_Verb:
2658 w += winding_line(pts, x, y);
2659 break;
2660 case SkPath::kQuad_Verb:
2661 w += winding_quad(pts, x, y);
2662 break;
2663 case SkPath::kCubic_Verb:
2664 w += winding_cubic(pts, x, y);
2665 break;
2666 case SkPath::kDone_Verb:
2667 done = true;
2668 break;
2669 }
2670 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002671
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002672 switch (this->getFillType()) {
2673 case SkPath::kEvenOdd_FillType:
2674 case SkPath::kInverseEvenOdd_FillType:
2675 w &= 1;
2676 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002677 default:
2678 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002679 }
2680 return SkToBool(w);
2681}
2682