blob: 9723db929aa0bbfafc0f661939ba088cf890febb [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
djsollen@google.com077348c2012-10-22 20:23:32 +00001945#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00001946 if (!fBoundsIsDirty) {
1947 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001948
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001949 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001950 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001951
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001952 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00001953 // if we're empty, fBounds may be empty but translated, so we can't
1954 // necessarily compare to bounds directly
1955 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1956 // be [2, 2, 2, 2]
1957 SkASSERT(bounds.isEmpty());
1958 SkASSERT(fBounds.isEmpty());
1959 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001960 if (bounds.isEmpty()) {
1961 SkASSERT(fBounds.isEmpty());
1962 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001963 if (!fBounds.isEmpty()) {
1964 SkASSERT(fBounds.contains(bounds));
1965 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001966 }
reed@android.come522ca52009-11-23 20:10:41 +00001967 }
1968 }
reed@google.com10296cc2011-09-21 12:29:05 +00001969
1970 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
1972 for (int i = 0; i < fPathRef->countVerbs(); i++) {
1973 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00001974 case kLine_Verb:
1975 mask |= kLine_SegmentMask;
1976 break;
1977 case kQuad_Verb:
1978 mask |= kQuad_SegmentMask;
1979 break;
1980 case kCubic_Verb:
1981 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001982 case kMove_Verb: // these verbs aren't included in the segment mask.
1983 case kClose_Verb:
1984 break;
1985 case kDone_Verb:
1986 SkDEBUGFAIL("Done verb shouldn't be recorded.");
1987 break;
1988 default:
1989 SkDEBUGFAIL("Unknown Verb");
1990 break;
reed@google.com10296cc2011-09-21 12:29:05 +00001991 }
1992 }
1993 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00001994#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00001995}
djsollen@google.com077348c2012-10-22 20:23:32 +00001996#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00001997
reed@google.com04863fa2011-05-15 04:08:24 +00001998///////////////////////////////////////////////////////////////////////////////
1999
reed@google.com0b7b9822011-05-16 12:29:27 +00002000static int sign(SkScalar x) { return x < 0; }
2001#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002002
reed@google.com04863fa2011-05-15 04:08:24 +00002003static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002004 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002005}
2006
2007// only valid for a single contour
2008struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00002009 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00002010 fSign = 0;
2011 // warnings
2012 fCurrPt.set(0, 0);
2013 fVec0.set(0, 0);
2014 fVec1.set(0, 0);
2015 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002016
2017 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002018 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002019 }
2020
2021 SkPath::Convexity getConvexity() const { return fConvexity; }
2022
2023 void addPt(const SkPoint& pt) {
2024 if (SkPath::kConcave_Convexity == fConvexity) {
2025 return;
2026 }
2027
2028 if (0 == fPtCount) {
2029 fCurrPt = pt;
2030 ++fPtCount;
2031 } else {
2032 SkVector vec = pt - fCurrPt;
2033 if (vec.fX || vec.fY) {
2034 fCurrPt = pt;
2035 if (++fPtCount == 2) {
2036 fFirstVec = fVec1 = vec;
2037 } else {
2038 SkASSERT(fPtCount > 2);
2039 this->addVec(vec);
2040 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002041
reed@google.com85b6e392011-05-15 20:25:17 +00002042 int sx = sign(vec.fX);
2043 int sy = sign(vec.fY);
2044 fDx += (sx != fSx);
2045 fDy += (sy != fSy);
2046 fSx = sx;
2047 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002048
reed@google.com85b6e392011-05-15 20:25:17 +00002049 if (fDx > 3 || fDy > 3) {
2050 fConvexity = SkPath::kConcave_Convexity;
2051 }
reed@google.com04863fa2011-05-15 04:08:24 +00002052 }
2053 }
2054 }
2055
2056 void close() {
2057 if (fPtCount > 2) {
2058 this->addVec(fFirstVec);
2059 }
2060 }
2061
2062private:
2063 void addVec(const SkVector& vec) {
2064 SkASSERT(vec.fX || vec.fY);
2065 fVec0 = fVec1;
2066 fVec1 = vec;
2067 int sign = CrossProductSign(fVec0, fVec1);
2068 if (0 == fSign) {
2069 fSign = sign;
2070 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002071 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002072 fConvexity = SkPath::kConcave_Convexity;
2073 }
2074 }
2075 }
2076
2077 SkPoint fCurrPt;
2078 SkVector fVec0, fVec1, fFirstVec;
2079 int fPtCount; // non-degenerate points
2080 int fSign;
2081 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00002082 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002083};
2084
2085SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
2086 SkPoint pts[4];
2087 SkPath::Verb verb;
2088 SkPath::Iter iter(path, true);
2089
2090 int contourCount = 0;
2091 int count;
2092 Convexicator state;
2093
2094 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2095 switch (verb) {
2096 case kMove_Verb:
2097 if (++contourCount > 1) {
2098 return kConcave_Convexity;
2099 }
2100 pts[1] = pts[0];
2101 count = 1;
2102 break;
2103 case kLine_Verb: count = 1; break;
2104 case kQuad_Verb: count = 2; break;
2105 case kCubic_Verb: count = 3; break;
2106 case kClose_Verb:
2107 state.close();
2108 count = 0;
2109 break;
2110 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002111 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00002112 return kConcave_Convexity;
2113 }
2114
2115 for (int i = 1; i <= count; i++) {
2116 state.addPt(pts[i]);
2117 }
2118 // early exit
2119 if (kConcave_Convexity == state.getConvexity()) {
2120 return kConcave_Convexity;
2121 }
2122 }
2123 return state.getConvexity();
2124}
reed@google.com69a99432012-01-10 18:00:10 +00002125
2126///////////////////////////////////////////////////////////////////////////////
2127
2128class ContourIter {
2129public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002130 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002131
2132 bool done() const { return fDone; }
2133 // if !done() then these may be called
2134 int count() const { return fCurrPtCount; }
2135 const SkPoint* pts() const { return fCurrPt; }
2136 void next();
2137
2138private:
2139 int fCurrPtCount;
2140 const SkPoint* fCurrPt;
2141 const uint8_t* fCurrVerb;
2142 const uint8_t* fStopVerbs;
2143 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002144 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002145};
2146
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002147ContourIter::ContourIter(const SkPathRef& pathRef) {
2148 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002149 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002150 fCurrPt = pathRef.points();
2151 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002152 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002153 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002154 this->next();
2155}
2156
2157void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002158 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002159 fDone = true;
2160 }
2161 if (fDone) {
2162 return;
2163 }
2164
2165 // skip pts of prev contour
2166 fCurrPt += fCurrPtCount;
2167
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002168 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002169 int ptCount = 1; // moveTo
2170 const uint8_t* verbs = fCurrVerb;
2171
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002172 for (--verbs; verbs > fStopVerbs; --verbs) {
2173 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002174 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002175 goto CONTOUR_END;
2176 case SkPath::kLine_Verb:
2177 ptCount += 1;
2178 break;
2179 case SkPath::kQuad_Verb:
2180 ptCount += 2;
2181 break;
2182 case SkPath::kCubic_Verb:
2183 ptCount += 3;
2184 break;
2185 default: // kClose_Verb, just keep going
2186 break;
2187 }
2188 }
2189CONTOUR_END:
2190 fCurrPtCount = ptCount;
2191 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002192 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002193}
2194
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002195// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002196static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002197 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2198 // We may get 0 when the above subtracts underflow. We expect this to be
2199 // very rare and lazily promote to double.
2200 if (0 == cross) {
2201 double p0x = SkScalarToDouble(p0.fX);
2202 double p0y = SkScalarToDouble(p0.fY);
2203
2204 double p1x = SkScalarToDouble(p1.fX);
2205 double p1y = SkScalarToDouble(p1.fY);
2206
2207 double p2x = SkScalarToDouble(p2.fX);
2208 double p2y = SkScalarToDouble(p2.fY);
2209
2210 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2211 (p1y - p0y) * (p2x - p0x));
2212
2213 }
2214 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002215}
2216
reed@google.comc1ea60a2012-01-31 15:15:36 +00002217// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002218static int find_max_y(const SkPoint pts[], int count) {
2219 SkASSERT(count > 0);
2220 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002221 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002222 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002223 SkScalar y = pts[i].fY;
2224 if (y > max) {
2225 max = y;
2226 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002227 }
2228 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002229 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002230}
2231
reed@google.comcabaf1d2012-01-11 21:03:05 +00002232static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2233 int i = index;
2234 for (;;) {
2235 i = (i + inc) % n;
2236 if (i == index) { // we wrapped around, so abort
2237 break;
2238 }
2239 if (pts[index] != pts[i]) { // found a different point, success!
2240 break;
2241 }
2242 }
2243 return i;
2244}
2245
reed@google.comc1ea60a2012-01-31 15:15:36 +00002246/**
2247 * Starting at index, and moving forward (incrementing), find the xmin and
2248 * xmax of the contiguous points that have the same Y.
2249 */
2250static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2251 int* maxIndexPtr) {
2252 const SkScalar y = pts[index].fY;
2253 SkScalar min = pts[index].fX;
2254 SkScalar max = min;
2255 int minIndex = index;
2256 int maxIndex = index;
2257 for (int i = index + 1; i < n; ++i) {
2258 if (pts[i].fY != y) {
2259 break;
2260 }
2261 SkScalar x = pts[i].fX;
2262 if (x < min) {
2263 min = x;
2264 minIndex = i;
2265 } else if (x > max) {
2266 max = x;
2267 maxIndex = i;
2268 }
2269 }
2270 *maxIndexPtr = maxIndex;
2271 return minIndex;
2272}
2273
reed@google.comac8543f2012-01-30 20:51:25 +00002274static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2275 if (dir) {
2276 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2277 }
2278 return true;
2279}
2280
reed@google.comc1ea60a2012-01-31 15:15:36 +00002281#if 0
2282#include "SkString.h"
2283#include "../utils/SkParsePath.h"
2284static void dumpPath(const SkPath& path) {
2285 SkString str;
2286 SkParsePath::ToSVGString(path, &str);
2287 SkDebugf("%s\n", str.c_str());
2288}
2289#endif
2290
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002291namespace {
2292// for use with convex_dir_test
2293double mul(double a, double b) { return a * b; }
2294SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2295double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2296SkScalar toScalar(SkScalar a) { return a; }
2297
2298// determines the winding direction of a convex polygon with the precision
2299// of T. CAST_SCALAR casts an SkScalar to T.
2300template <typename T, T (CAST_SCALAR)(SkScalar)>
2301bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2302 // we find the first three points that form a non-degenerate
2303 // triangle. If there are no such points then the path is
2304 // degenerate. The first is always point 0. Now we find the second
2305 // point.
2306 int i = 0;
2307 enum { kX = 0, kY = 1 };
2308 T v0[2];
2309 while (1) {
2310 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2311 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2312 if (v0[kX] || v0[kY]) {
2313 break;
2314 }
2315 if (++i == n - 1) {
2316 return false;
2317 }
2318 }
2319 // now find a third point that is not colinear with the first two
2320 // points and check the orientation of the triangle (which will be
2321 // the same as the orientation of the path).
2322 for (++i; i < n; ++i) {
2323 T v1[2];
2324 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2325 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2326 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2327 if (0 != cross) {
2328 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2329 return true;
2330 }
2331 }
2332 return false;
2333}
2334}
2335
reed@google.comac8543f2012-01-30 20:51:25 +00002336/*
2337 * We loop through all contours, and keep the computed cross-product of the
2338 * contour that contained the global y-max. If we just look at the first
2339 * contour, we may find one that is wound the opposite way (correctly) since
2340 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2341 * that is outer most (or at least has the global y-max) before we can consider
2342 * its cross product.
2343 */
reed@google.com69a99432012-01-10 18:00:10 +00002344bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002345// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002346 // don't want to pay the cost for computing this if it
2347 // is unknown, so we don't call isConvex()
2348 const Convexity conv = this->getConvexityOrUnknown();
2349
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002350 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002351
reed@google.comac8543f2012-01-30 20:51:25 +00002352 // initialize with our logical y-min
2353 SkScalar ymax = this->getBounds().fTop;
2354 SkScalar ymaxCross = 0;
2355
reed@google.com69a99432012-01-10 18:00:10 +00002356 for (; !iter.done(); iter.next()) {
2357 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002358 if (n < 3) {
2359 continue;
2360 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002361
reed@google.comcabaf1d2012-01-11 21:03:05 +00002362 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002363 SkScalar cross = 0;
2364 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002365 // We try first at scalar precision, and then again at double
2366 // precision. This is because the vectors computed between distant
2367 // points may lose too much precision.
2368 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2369 return true;
2370 }
2371 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2372 return true;
2373 } else {
2374 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002375 }
2376 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002377 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002378 if (pts[index].fY < ymax) {
2379 continue;
2380 }
2381
reed@google.comc1ea60a2012-01-31 15:15:36 +00002382 // If there is more than 1 distinct point at the y-max, we take the
2383 // x-min and x-max of them and just subtract to compute the dir.
2384 if (pts[(index + 1) % n].fY == pts[index].fY) {
2385 int maxIndex;
2386 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002387 if (minIndex == maxIndex) {
2388 goto TRY_CROSSPROD;
2389 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002390 SkASSERT(pts[minIndex].fY == pts[index].fY);
2391 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2392 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2393 // we just subtract the indices, and let that auto-convert to
2394 // SkScalar, since we just want - or + to signal the direction.
2395 cross = minIndex - maxIndex;
2396 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002397 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002398 // Find a next and prev index to use for the cross-product test,
2399 // but we try to find pts that form non-zero vectors from pts[index]
2400 //
2401 // Its possible that we can't find two non-degenerate vectors, so
2402 // we have to guard our search (e.g. all the pts could be in the
2403 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002404
reed@google.comc1ea60a2012-01-31 15:15:36 +00002405 // we pass n - 1 instead of -1 so we don't foul up % operator by
2406 // passing it a negative LH argument.
2407 int prev = find_diff_pt(pts, index, n, n - 1);
2408 if (prev == index) {
2409 // completely degenerate, skip to next contour
2410 continue;
2411 }
2412 int next = find_diff_pt(pts, index, n, 1);
2413 SkASSERT(next != index);
2414 cross = cross_prod(pts[prev], pts[index], pts[next]);
2415 // if we get a zero, but the pts aren't on top of each other, then
2416 // we can just look at the direction
2417 if (0 == cross) {
2418 // construct the subtract so we get the correct Direction below
2419 cross = pts[index].fX - pts[next].fX;
2420 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002421 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002422
reed@google.comac8543f2012-01-30 20:51:25 +00002423 if (cross) {
2424 // record our best guess so far
2425 ymax = pts[index].fY;
2426 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002427 }
reed@google.com69a99432012-01-10 18:00:10 +00002428 }
2429 }
reed@google.com69a99432012-01-10 18:00:10 +00002430
reed@google.comac8543f2012-01-30 20:51:25 +00002431 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2432}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002433
2434///////////////////////////////////////////////////////////////////////////////
2435
2436static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2437 SkScalar D, SkScalar t) {
2438 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2439}
2440
2441static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2442 SkScalar t) {
2443 SkScalar A = c3 + 3*(c1 - c2) - c0;
2444 SkScalar B = 3*(c2 - c1 - c1 + c0);
2445 SkScalar C = 3*(c1 - c0);
2446 SkScalar D = c0;
2447 return eval_cubic_coeff(A, B, C, D, t);
2448}
2449
2450/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2451 t value such that cubic(t) = target
2452 */
2453static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2454 SkScalar target, SkScalar* t) {
2455 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2456 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002457
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002458 SkScalar D = c0 - target;
2459 SkScalar A = c3 + 3*(c1 - c2) - c0;
2460 SkScalar B = 3*(c2 - c1 - c1 + c0);
2461 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002462
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002463 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2464 SkScalar minT = 0;
2465 SkScalar maxT = SK_Scalar1;
2466 SkScalar mid;
2467 int i;
2468 for (i = 0; i < 16; i++) {
2469 mid = SkScalarAve(minT, maxT);
2470 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2471 if (delta < 0) {
2472 minT = mid;
2473 delta = -delta;
2474 } else {
2475 maxT = mid;
2476 }
2477 if (delta < TOLERANCE) {
2478 break;
2479 }
2480 }
2481 *t = mid;
2482 return true;
2483}
2484
2485template <size_t N> static void find_minmax(const SkPoint pts[],
2486 SkScalar* minPtr, SkScalar* maxPtr) {
2487 SkScalar min, max;
2488 min = max = pts[0].fX;
2489 for (size_t i = 1; i < N; ++i) {
2490 min = SkMinScalar(min, pts[i].fX);
2491 max = SkMaxScalar(max, pts[i].fX);
2492 }
2493 *minPtr = min;
2494 *maxPtr = max;
2495}
2496
2497static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2498 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002499
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002500 int dir = 1;
2501 if (pts[0].fY > pts[3].fY) {
2502 storage[0] = pts[3];
2503 storage[1] = pts[2];
2504 storage[2] = pts[1];
2505 storage[3] = pts[0];
2506 pts = storage;
2507 dir = -1;
2508 }
2509 if (y < pts[0].fY || y >= pts[3].fY) {
2510 return 0;
2511 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002512
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002513 // quickreject or quickaccept
2514 SkScalar min, max;
2515 find_minmax<4>(pts, &min, &max);
2516 if (x < min) {
2517 return 0;
2518 }
2519 if (x > max) {
2520 return dir;
2521 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002522
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002523 // compute the actual x(t) value
2524 SkScalar t, xt;
2525 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2526 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2527 } else {
2528 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2529 xt = y < mid ? pts[0].fX : pts[3].fX;
2530 }
2531 return xt < x ? dir : 0;
2532}
2533
2534static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2535 SkPoint dst[10];
2536 int n = SkChopCubicAtYExtrema(pts, dst);
2537 int w = 0;
2538 for (int i = 0; i <= n; ++i) {
2539 w += winding_mono_cubic(&dst[i * 3], x, y);
2540 }
2541 return w;
2542}
2543
2544static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2545 SkScalar y0 = pts[0].fY;
2546 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002547
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002548 int dir = 1;
2549 if (y0 > y2) {
2550 SkTSwap(y0, y2);
2551 dir = -1;
2552 }
2553 if (y < y0 || y >= y2) {
2554 return 0;
2555 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002556
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002557 // bounds check on X (not required. is it faster?)
2558#if 0
2559 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2560 return 0;
2561 }
2562#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002563
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002564 SkScalar roots[2];
2565 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2566 2 * (pts[1].fY - pts[0].fY),
2567 pts[0].fY - y,
2568 roots);
2569 SkASSERT(n <= 1);
2570 SkScalar xt;
2571 if (0 == n) {
2572 SkScalar mid = SkScalarAve(y0, y2);
2573 // Need [0] and [2] if dir == 1
2574 // and [2] and [0] if dir == -1
2575 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2576 } else {
2577 SkScalar t = roots[0];
2578 SkScalar C = pts[0].fX;
2579 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2580 SkScalar B = 2 * (pts[1].fX - C);
2581 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2582 }
2583 return xt < x ? dir : 0;
2584}
2585
2586static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2587 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2588 if (y0 == y1) {
2589 return true;
2590 }
2591 if (y0 < y1) {
2592 return y1 <= y2;
2593 } else {
2594 return y1 >= y2;
2595 }
2596}
2597
2598static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2599 SkPoint dst[5];
2600 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002601
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002602 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2603 n = SkChopQuadAtYExtrema(pts, dst);
2604 pts = dst;
2605 }
2606 int w = winding_mono_quad(pts, x, y);
2607 if (n > 0) {
2608 w += winding_mono_quad(&pts[2], x, y);
2609 }
2610 return w;
2611}
2612
2613static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2614 SkScalar x0 = pts[0].fX;
2615 SkScalar y0 = pts[0].fY;
2616 SkScalar x1 = pts[1].fX;
2617 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002618
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002619 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002620
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002621 int dir = 1;
2622 if (y0 > y1) {
2623 SkTSwap(y0, y1);
2624 dir = -1;
2625 }
2626 if (y < y0 || y >= y1) {
2627 return 0;
2628 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002629
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002630 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2631 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002632
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002633 if (SkScalarSignAsInt(cross) == dir) {
2634 dir = 0;
2635 }
2636 return dir;
2637}
2638
2639bool SkPath::contains(SkScalar x, SkScalar y) const {
2640 bool isInverse = this->isInverseFillType();
2641 if (this->isEmpty()) {
2642 return isInverse;
2643 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002644
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002645 const SkRect& bounds = this->getBounds();
2646 if (!bounds.contains(x, y)) {
2647 return isInverse;
2648 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002649
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002650 SkPath::Iter iter(*this, true);
2651 bool done = false;
2652 int w = 0;
2653 do {
2654 SkPoint pts[4];
2655 switch (iter.next(pts, false)) {
2656 case SkPath::kMove_Verb:
2657 case SkPath::kClose_Verb:
2658 break;
2659 case SkPath::kLine_Verb:
2660 w += winding_line(pts, x, y);
2661 break;
2662 case SkPath::kQuad_Verb:
2663 w += winding_quad(pts, x, y);
2664 break;
2665 case SkPath::kCubic_Verb:
2666 w += winding_cubic(pts, x, y);
2667 break;
2668 case SkPath::kDone_Verb:
2669 done = true;
2670 break;
2671 }
2672 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002673
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002674 switch (this->getFillType()) {
2675 case SkPath::kEvenOdd_FillType:
2676 case SkPath::kInverseEvenOdd_FillType:
2677 w &= 1;
2678 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002679 default:
2680 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 }
2682 return SkToBool(w);
2683}
2684