Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(156)

Unified Diff: src/core/SkPathRef.cpp

Issue 25787002: Move more of SkPath into SkPathRef (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: cleaned up Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/core/SkPath.cpp ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/core/SkPathRef.cpp
===================================================================
--- src/core/SkPathRef.cpp (revision 11888)
+++ src/core/SkPathRef.cpp (working copy)
@@ -25,6 +25,7 @@
}
fPathRef = *pathRef;
fPathRef->fGenerationID = 0;
+ fPathRef->fIsOval = false;
SkDEBUGCODE(sk_atomic_inc(&fPathRef->fEditorsAttached);)
}
@@ -35,6 +36,7 @@
return pts;
}
+
//////////////////////////////////////////////////////////////////////////////
void SkPathRef::CreateTransformedCopy(SkAutoTUnref<SkPathRef>* dst,
const SkPathRef& src,
@@ -87,6 +89,29 @@
(*dst)->fBoundsIsDirty = true;
}
+ (*dst)->fLastMoveToIndex = src.fLastMoveToIndex;
+ (*dst)->fSegmentMask = src.fSegmentMask;
+ (*dst)->fConvexity = src.fConvexity;
+
+ if (SkPath::kUnknown_Direction == src.fDirection) {
+ (*dst)->fDirection = SkPath::kUnknown_Direction;
+ } else {
+ SkScalar det2x2 =
+ SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
+ SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
+ if (det2x2 < 0) {
+ (*dst)->fDirection = SkPath::OppositeDirection(
+ static_cast<SkPath::Direction>(src.getDirection()));
+ } else if (det2x2 > 0) {
+ (*dst)->fDirection = src.fDirection;
+ } else {
+ (*dst)->fDirection = SkPath::kUnknown_Direction;
+ }
+ }
+
+ // It's an oval only if it stays a rect.
+ (*dst)->fIsOval = src.fIsOval && matrix.rectStaysRect();
+
SkDEBUGCODE((*dst)->validate();)
}
@@ -96,15 +121,12 @@
#endif
) {
SkPathRef* ref = SkNEW(SkPathRef);
+ int32_t packed;
#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
if (newFormat) {
#endif
- int32_t packed = buffer->readU32();
-
- ref->fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
+ packed = buffer->readU32();
#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
- } else {
- ref->fIsFinite = (oldPacked >> SkPath::kOldIsFinite_SerializationShift) & 1;
}
#endif
@@ -114,6 +136,24 @@
int32_t conicCount = buffer->readS32();
ref->resetToSize(verbCount, pointCount, conicCount);
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+ if (newFormat) {
+#endif
+ ref->fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
+ ref->fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
+ ref->fIsOval = (packed >> kIsOval_SerializationShift) & 1;
+ ref->fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
+ ref->fDirection = (packed >> kDirection_SerializationShift) & 0x3;
+#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
+ } else {
+ ref->fSegmentMask = (oldPacked >> SkPath::kOldSegmentMask_SerializationShift) & 0xF;
+ ref->fConvexity = (oldPacked >> SkPath::kOldConvexity_SerializationShift) & 0xFF;
+ ref->fIsOval = (oldPacked >> SkPath::kOldIsOval_SerializationShift) & 1;
+ ref->fIsFinite = (oldPacked >> SkPath::kOldIsFinite_SerializationShift) & 1;
+ ref->fDirection = (oldPacked >> SkPath::kOldDirection_SerializationShift) & 0x3;
+ }
+#endif
+
SkASSERT(verbCount == ref->countVerbs());
SkASSERT(pointCount == ref->countPoints());
SkASSERT(conicCount == ref->fConicWeights.count());
@@ -122,9 +162,54 @@
buffer->read(ref->fConicWeights.begin(), conicCount * sizeof(SkScalar));
buffer->read(&ref->fBounds, sizeof(SkRect));
ref->fBoundsIsDirty = false;
+
+#ifdef SK_DEBUG_PATH
+ int lastMoveToIndex = kINITIAL_LASTMOVETOINDEX_VALUE;
+ int pointCnt = 0;
+
+ for (int i = 0; i < ref->fVerbCnt; ++i) {
+ switch (ref->fVerbs[~i]) {
+ case kMove_Verb:
+ lastMoveToIndex = pointCnt;
+ ++pointCnt;
+ break;
+ case kLine_Verb:
+ ++pointCnt;
+ break;
+ case kQuad_Verb:
+ pointCnt += 2;
+ break;
+ case kConic_Verb:
+ pointCnt += 2;
+ break;
+ case kCubic_Verb:
+ pointCnt += 3;
+ break;
+ case kClose_Verb:
+ if (lastMoveToIndex >= 0) {
+ lastMoveToIndex = ~lastMoveToIndex;
+ }
+ break;
+ case kDone_Verb:
+ // fall through
+ default:
+ SkDEBUGFAIL("default is not reached");
+ }
+ }
+ ref->fLastMoveToIndex = lastMoveToIndex;
+#endif
+
return ref;
}
+int /* SkPath::Convexity */ SkPathRef::getConvexity() const {
+ if (SkPath::kUnknown_Convexity != fConvexity) {
+ return fConvexity;
+ } else {
+ return this->internalGetConvexity();
+ }
+}
+
void SkPathRef::Rewind(SkAutoTUnref<SkPathRef>* pathRef) {
if ((*pathRef)->unique()) {
SkDEBUGCODE((*pathRef)->validate();)
@@ -134,6 +219,11 @@
(*pathRef)->fFreeSpace = (*pathRef)->currSize();
(*pathRef)->fGenerationID = 0;
(*pathRef)->fConicWeights.rewind();
+ (*pathRef)->fLastMoveToIndex = kINITIAL_LASTMOVETOINDEX_VALUE;
+ (*pathRef)->fSegmentMask = 0;
+ (*pathRef)->fConvexity = SkPath::kUnknown_Convexity;
+ (*pathRef)->fDirection = SkPath::kUnknown_Direction;
+ (*pathRef)->fIsOval = false;
SkDEBUGCODE((*pathRef)->validate();)
} else {
int oldVCnt = (*pathRef)->countVerbs();
@@ -146,6 +236,15 @@
bool SkPathRef::operator== (const SkPathRef& ref) const {
SkDEBUGCODE(this->validate();)
SkDEBUGCODE(ref.validate();)
+ // note: don't need to look at isConvex or bounds, since just comparing the
+ // raw data is sufficient.
+
+ // We explicitly check fSegmentMask as a quick-reject. We could skip it,
+ // since it is only a cache of info in the fVerbs, but its a fast way to
+ // notice a difference
+ if (fSegmentMask != ref.fSegmentMask) {
+ return false;
+ }
bool genIDMatch = fGenerationID && fGenerationID == ref.fGenerationID;
#ifdef SK_RELEASE
if (genIDMatch) {
@@ -183,7 +282,7 @@
return true;
}
-void SkPathRef::writeToBuffer(SkWBuffer* buffer) {
+void SkPathRef::writeToBuffer(SkWBuffer* buffer) const {
SkDEBUGCODE(this->validate();)
SkDEBUGCODE(size_t beforePos = buffer->pos();)
@@ -191,7 +290,11 @@
// and fIsFinite are computed.
const SkRect& bounds = this->getBounds();
- int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift);
+ int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
+ ((fIsOval & 1) << kIsOval_SerializationShift) |
+ (fConvexity << kConvexity_SerializationShift) |
+ (fSegmentMask << kSegmentMask_SerializationShift) |
+ (fDirection << kDirection_SerializationShift);
buffer->write32(packed);
// TODO: write gen ID here. Problem: We don't know if we're cross process or not from
@@ -208,7 +311,7 @@
SkASSERT(buffer->pos() - beforePos == (size_t) this->writeSize());
}
-uint32_t SkPathRef::writeSize() {
+uint32_t SkPathRef::writeSize() const {
return uint32_t(5 * sizeof(uint32_t) +
fVerbCnt * sizeof(uint8_t) +
fPointCnt * sizeof(SkPoint) +
@@ -216,6 +319,23 @@
sizeof(SkRect));
}
+SkPathRef::SkPathRef() {
+ fBoundsIsDirty = true; // this also invalidates fIsFinite
+ fPointCnt = 0;
+ fVerbCnt = 0;
+ fVerbs = NULL;
+ fPoints = NULL;
+ fFreeSpace = 0;
+ fGenerationID = kEmptyGenID;
+ SkDEBUGCODE(fEditorsAttached = 0;)
+ fLastMoveToIndex = kINITIAL_LASTMOVETOINDEX_VALUE;
+ fSegmentMask = 0;
+ fConvexity = SkPath::kUnknown_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
+ fIsOval = false;
+ SkDEBUGCODE(this->validate();)
+}
+
void SkPathRef::copy(const SkPathRef& ref,
int additionalReserveVerbs,
int additionalReservePoints) {
@@ -233,45 +353,201 @@
fBounds = ref.fBounds;
fIsFinite = ref.fIsFinite;
}
+ fLastMoveToIndex = ref.fLastMoveToIndex;
+ fSegmentMask = ref.fSegmentMask;
+ fConvexity = ref.fConvexity;
+ fDirection = ref.fDirection;
+ fIsOval = ref.fIsOval;
SkDEBUGCODE(this->validate();)
}
-SkPoint* SkPathRef::growForVerb(int /* SkPath::Verb*/ verb) {
+void SkPathRef::resetToSize(int verbCount, int pointCount, int conicCount,
+ int reserveVerbs, int reservePoints) {
SkDEBUGCODE(this->validate();)
+ fBoundsIsDirty = true; // this also invalidates fIsFinite
+ fGenerationID = 0;
+
+ fLastMoveToIndex = kINITIAL_LASTMOVETOINDEX_VALUE;
+ fSegmentMask = 0;
+ fConvexity = SkPath::kUnknown_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
+ fIsOval = false;
+
+ size_t newSize = sizeof(uint8_t) * verbCount + sizeof(SkPoint) * pointCount;
+ size_t newReserve = sizeof(uint8_t) * reserveVerbs + sizeof(SkPoint) * reservePoints;
+ size_t minSize = newSize + newReserve;
+
+ ptrdiff_t sizeDelta = this->currSize() - minSize;
+
+ if (sizeDelta < 0 || static_cast<size_t>(sizeDelta) >= 3 * minSize) {
+ sk_free(fPoints);
+ fPoints = NULL;
+ fVerbs = NULL;
+ fFreeSpace = 0;
+ fVerbCnt = 0;
+ fPointCnt = 0;
+ this->makeSpace(minSize);
+ fVerbCnt = verbCount;
+ fPointCnt = pointCount;
+ fFreeSpace -= newSize;
+ } else {
+ fPointCnt = pointCount;
+ fVerbCnt = verbCount;
+ fFreeSpace = this->currSize() - minSize;
+ }
+ fConicWeights.setCount(conicCount);
+ SkDEBUGCODE(this->validate();)
+}
+
+// This method assumes space has already been allocated for the new
+// verb and point.
+void SkPathRef::injectMove() {
+ SkScalar x, y;
+ if (this->countVerbs() == 0) {
+ x = y = 0;
+ } else {
+ const SkPoint& pt = this->atPoint(~fLastMoveToIndex);
+ x = pt.fX;
+ y = pt.fY;
+ }
+ fPoints[fPointCnt].set(x, y);
+ fLastMoveToIndex = fPointCnt;
+ ++fPointCnt;
+ fVerbs[~fVerbCnt] = SkPath::kMove_Verb;
+ ++fVerbCnt;
+}
+
+SkPoint* SkPathRef::growForLines(int numLines) {
+ // This value is just made-up for now. When numLines is 3, calling memset was much
+ // slower than just writing the loop. This seems odd, and hopefully in the
+ // future this will appear to have been a fluke...
+ static const unsigned int kMIN_COUNT_FOR_MEMSET_TO_BE_FAST = 16;
+
+ // TODO: The following isn't hyper-optimized for the lines case since
+ // it should be expanded to handle quads, conics and cubics
+ SkDEBUGCODE(this->validate();)
+ bool dirtyAfterEdit = true;
+ bool moveInjectionNeeded = false;
+
+ moveInjectionNeeded = fLastMoveToIndex < 0;
+
+ size_t space = numLines * sizeof(uint8_t) + numLines * sizeof (SkPoint);
+ if (moveInjectionNeeded) {
+ space += sizeof(uint8_t) + sizeof(SkPoint);
+ }
+ this->makeSpace(space);
+ if (moveInjectionNeeded) {
+ SkASSERT(dirtyAfterEdit);
+ this->injectMove();
+ }
+ SkPoint* ret = fPoints + fPointCnt;
+ uint8_t* vb = fVerbs - fVerbCnt;
+
+ // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
+ // be 0, the compiler will remove the test/branch entirely.
+ if ((unsigned)numLines >= kMIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
+ memset(vb - numLines, SkPath::kLine_Verb, numLines);
+ } else {
+ for (int i = 0; i < numLines; ++i) {
+ vb[~i] = SkPath::kLine_Verb;
+ }
+ }
+ fSegmentMask |= SkPath::kLine_SegmentMask;
+
+ fVerbCnt += numLines;
+ fPointCnt += numLines;
+ fFreeSpace -= space;
+ fBoundsIsDirty = true; // this also invalidates fIsFinite
+
+ if (dirtyAfterEdit) {
+ fConvexity = SkPath::kUnknown_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
+ fIsOval = false;
+ }
+
+ SkDEBUGCODE(this->validate();)
+ return ret;
+}
+
+// In some cases we need to inject a leading moveTo before we add points
+// for lineTo, quadTo, conicTo, cubicTo
+//
+// SkPath path; path.lineTo(...); <--- need a leading moveTo(0, 0)
+// SkPath path; ... path.close(); path.lineTo(...) <-- need a moveTo(previous moveTo)
+SkPoint* SkPathRef::growForVerb(int /* Verb */ verb) {
+ SkDEBUGCODE(this->validate();)
int pCnt;
+ bool dirtyAfterEdit = true;
+ bool moveInjectionNeeded = false;
+ uint8_t newMask = 0;
+ int newLastMoveToIndex = fLastMoveToIndex;
+
switch (verb) {
case SkPath::kMove_Verb:
+ // remember the index
+ newLastMoveToIndex = this->countPoints();
pCnt = 1;
+ dirtyAfterEdit = false;
break;
case SkPath::kLine_Verb:
+ moveInjectionNeeded = fLastMoveToIndex < 0;
+ newMask = SkPath::kLine_SegmentMask;
pCnt = 1;
break;
case SkPath::kQuad_Verb:
- // fall through
+ moveInjectionNeeded = fLastMoveToIndex < 0;
+ newMask = SkPath::kQuad_SegmentMask;
+ pCnt = 2;
+ break;
case SkPath::kConic_Verb:
+ moveInjectionNeeded = fLastMoveToIndex < 0;
+ newMask = SkPath::kConic_SegmentMask;
pCnt = 2;
break;
case SkPath::kCubic_Verb:
+ moveInjectionNeeded = fLastMoveToIndex < 0;
+ newMask = SkPath::kCubic_SegmentMask;
pCnt = 3;
break;
case SkPath::kClose_Verb:
+ // signal that we need a moveTo to follow us (unless we're done)
+ newLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
pCnt = 0;
+ dirtyAfterEdit = false;
break;
case SkPath::kDone_Verb:
SkDEBUGFAIL("growForVerb called for kDone");
// fall through
default:
SkDEBUGFAIL("default is not reached");
+ dirtyAfterEdit = false;
pCnt = 0;
}
size_t space = sizeof(uint8_t) + pCnt * sizeof (SkPoint);
+ if (moveInjectionNeeded) {
+ space += sizeof(uint8_t) + sizeof(SkPoint);
+ }
this->makeSpace(space);
- this->fVerbs[~fVerbCnt] = verb;
+ fLastMoveToIndex = newLastMoveToIndex;
+ if (moveInjectionNeeded) {
+ SkASSERT(dirtyAfterEdit);
+ this->injectMove();
+ } else {
+ fLastMoveToIndex = newLastMoveToIndex;
+ }
+ fVerbs[~fVerbCnt] = verb;
+ fSegmentMask |= newMask;
SkPoint* ret = fPoints + fPointCnt;
fVerbCnt += 1;
fPointCnt += pCnt;
fFreeSpace -= space;
fBoundsIsDirty = true; // this also invalidates fIsFinite
+ if (dirtyAfterEdit) {
+ fConvexity = SkPath::kUnknown_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
+ fIsOval = false;
+ }
+
SkDEBUGCODE(this->validate();)
return ret;
}
@@ -306,7 +582,6 @@
SkASSERT(this->currSize() ==
fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt);
-#ifdef SK_DEBUG
if (!fBoundsIsDirty && !fBounds.isEmpty()) {
bool isFinite = true;
for (int i = 0; i < fPointCnt; ++i) {
@@ -320,6 +595,553 @@
}
SkASSERT(SkToBool(fIsFinite) == isFinite);
}
+
+#ifdef SK_DEBUG_PATH
+ if (!fBoundsIsDirty) {
+ SkRect bounds;
+
+ bool isFinite = ComputePtBounds(&bounds, fPoints, fPointCnt);
+ SkASSERT(SkToBool(fIsFinite) == isFinite);
+
+ if (fPointCnt <= 1) {
+ // if we're empty, fBounds may be empty but translated, so we can't
+ // necessarily compare to bounds directly
+ // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
+ // be [2, 2, 2, 2]
+ SkASSERT(bounds.isEmpty());
+ SkASSERT(fBounds.isEmpty());
+ } else {
+ if (bounds.isEmpty()) {
+ SkASSERT(fBounds.isEmpty());
+ } else {
+ if (!fBounds.isEmpty()) {
+ SkASSERT(fBounds.contains(bounds));
+ }
+ }
+ }
+ }
+
+ uint32_t mask = 0;
+ int pointCnt = 0;
+ int lastMoveToIndex = ~0;
+ for (int i = 0; i < fVerbCnt; i++) {
+ switch (fVerbs[~i]) {
+ case kMove_Verb:
+ lastMoveToIndex = pointCnt;
+ ++pointCnt;
+ break;
+ case kLine_Verb:
+ mask |= kLine_SegmentMask;
+ ++pointCnt;
+ break;
+ case kQuad_Verb:
+ mask |= kQuad_SegmentMask;
+ pointCnt += 2;
+ break;
+ case kConic_Verb:
+ mask |= kConic_SegmentMask;
+ pointCnt += 2;
+ break;
+ case kCubic_Verb:
+ mask |= kCubic_SegmentMask;
+ pointCnt += 3;
+ break;
+ case kClose_Verb:
+ if (lastMoveToIndex >= 0) {
+ lastMoveToIndex = ~lastMoveToIndex;
+ }
+ break;
+ case kDone_Verb:
+ SkDEBUGFAIL("Done verb shouldn't be recorded.");
+ break;
+ default:
+ SkDEBUGFAIL("Unknown Verb");
+ break;
+ }
+ }
+ SkASSERT(mask == fSegmentMask);
+ SkASSERT(lastMoveToIndex == fLastMoveToIndex);
+#endif // SK_DEBUG_PATH
+}
#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int sign(SkScalar x) { return x < 0; }
+#define kValueNeverReturnedBySign 2
+
+static int CrossProductSign(const SkVector& a, const SkVector& b) {
+ return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
}
+
+// only valid for a single contour
+struct Convexicator {
+ Convexicator()
+ : fPtCount(0)
+ , fConvexity(SkPath::kConvex_Convexity)
+ , fDirection(SkPath::kUnknown_Direction) {
+ fSign = 0;
+ // warnings
+ fCurrPt.set(0, 0);
+ fVec0.set(0, 0);
+ fVec1.set(0, 0);
+ fFirstVec.set(0, 0);
+
+ fDx = fDy = 0;
+ fSx = fSy = kValueNeverReturnedBySign;
+ }
+
+ SkPath::Convexity getConvexity() const { return fConvexity; }
+
+ /** The direction returned is only valid if the path is determined convex */
+ SkPath::Direction getDirection() const { return fDirection; }
+
+ void addPt(const SkPoint& pt) {
+ if (SkPath::kConcave_Convexity == fConvexity) {
+ return;
+ }
+
+ if (0 == fPtCount) {
+ fCurrPt = pt;
+ ++fPtCount;
+ } else {
+ SkVector vec = pt - fCurrPt;
+ if (vec.fX || vec.fY) {
+ fCurrPt = pt;
+ if (++fPtCount == 2) {
+ fFirstVec = fVec1 = vec;
+ } else {
+ SkASSERT(fPtCount > 2);
+ this->addVec(vec);
+ }
+
+ int sx = sign(vec.fX);
+ int sy = sign(vec.fY);
+ fDx += (sx != fSx);
+ fDy += (sy != fSy);
+ fSx = sx;
+ fSy = sy;
+
+ if (fDx > 3 || fDy > 3) {
+ fConvexity = SkPath::kConcave_Convexity;
+ }
+ }
+ }
+ }
+
+ void close() {
+ if (fPtCount > 2) {
+ this->addVec(fFirstVec);
+ }
+ }
+
+private:
+ void addVec(const SkVector& vec) {
+ SkASSERT(vec.fX || vec.fY);
+ fVec0 = fVec1;
+ fVec1 = vec;
+ int sign = CrossProductSign(fVec0, fVec1);
+ if (0 == fSign) {
+ fSign = sign;
+ if (1 == sign) {
+ fDirection = SkPath::kCW_Direction;
+ } else if (-1 == sign) {
+ fDirection = SkPath::kCCW_Direction;
+ }
+ } else if (sign) {
+ if (fSign != sign) {
+ fConvexity = SkPath::kConcave_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
+ }
+ }
+ }
+
+ SkPoint fCurrPt;
+ SkVector fVec0, fVec1, fFirstVec;
+ int fPtCount; // non-degenerate points
+ int fSign;
+ SkPath::Convexity fConvexity;
+ SkPath::Direction fDirection;
+ int fDx, fDy, fSx, fSy;
+};
+
+int /* SkPath::Convexity */ SkPathRef::internalGetConvexity() const {
+ SkASSERT(SkPath::kUnknown_Convexity == fConvexity);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ SkPath::Iter iter(this, true);
+
+ int contourCount = 0;
+ int count;
+ Convexicator state;
+
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (++contourCount > 1) {
+ fConvexity = SkPath::kConcave_Convexity;
+ return SkPath::kConcave_Convexity;
+ }
+ pts[1] = pts[0];
+ count = 1;
+ break;
+ case SkPath::kLine_Verb: count = 1; break;
+ case SkPath::kQuad_Verb: count = 2; break;
+ case SkPath::kConic_Verb: count = 2; break;
+ case SkPath::kCubic_Verb: count = 3; break;
+ case SkPath::kClose_Verb:
+ state.close();
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ fConvexity = SkPath::kConcave_Convexity;
+ return SkPath::kConcave_Convexity;
+ }
+
+ for (int i = 1; i <= count; i++) {
+ state.addPt(pts[i]);
+ }
+ // early exit
+ if (SkPath::kConcave_Convexity == state.getConvexity()) {
+ fConvexity = SkPath::kConcave_Convexity;
+ return SkPath::kConcave_Convexity;
+ }
+ }
+ fConvexity = state.getConvexity();
+ if (SkPath::kConvex_Convexity == fConvexity && SkPath::kUnknown_Direction == fDirection) {
+ fDirection = state.getDirection();
+ }
+ return fConvexity;
+}
+
+class ContourIter {
+public:
+ ContourIter(const SkPathRef& pathRef);
+
+ bool done() const { return fDone; }
+ // if !done() then these may be called
+ int count() const { return fCurrPtCount; }
+ const SkPoint* pts() const { return fCurrPt; }
+ void next();
+
+private:
+ int fCurrPtCount;
+ const SkPoint* fCurrPt;
+ const uint8_t* fCurrVerb;
+ const uint8_t* fStopVerbs;
+ const SkScalar* fCurrConicWeight;
+ bool fDone;
+ SkDEBUGCODE(int fContourCounter;)
+};
+
+ContourIter::ContourIter(const SkPathRef& pathRef) {
+ fStopVerbs = pathRef.verbsMemBegin();
+ fDone = false;
+ fCurrPt = pathRef.points();
+ fCurrVerb = pathRef.verbs();
+ fCurrConicWeight = pathRef.conicWeights();
+ fCurrPtCount = 0;
+ SkDEBUGCODE(fContourCounter = 0;)
+ this->next();
+}
+
+void ContourIter::next() {
+ if (fCurrVerb <= fStopVerbs) {
+ fDone = true;
+ }
+ if (fDone) {
+ return;
+ }
+
+ // skip pts of prev contour
+ fCurrPt += fCurrPtCount;
+
+ SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
+ int ptCount = 1; // moveTo
+ const uint8_t* verbs = fCurrVerb;
+
+ for (--verbs; verbs > fStopVerbs; --verbs) {
+ switch (verbs[~0]) {
+ case SkPath::kMove_Verb:
+ goto CONTOUR_END;
+ case SkPath::kLine_Verb:
+ ptCount += 1;
+ break;
+ case SkPath::kConic_Verb:
+ fCurrConicWeight += 1;
+ // fall-through
+ case SkPath::kQuad_Verb:
+ ptCount += 2;
+ break;
+ case SkPath::kCubic_Verb:
+ ptCount += 3;
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ default:
+ SkDEBUGFAIL("unexpected verb");
+ break;
+ }
+ }
+CONTOUR_END:
+ fCurrPtCount = ptCount;
+ fCurrVerb = verbs;
+ SkDEBUGCODE(++fContourCounter;)
+}
+
+// returns cross product of (p1 - p0) and (p2 - p0)
+static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
+ SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
+ // We may get 0 when the above subtracts underflow. We expect this to be
+ // very rare and lazily promote to double.
+ if (0 == cross) {
+ double p0x = SkScalarToDouble(p0.fX);
+ double p0y = SkScalarToDouble(p0.fY);
+
+ double p1x = SkScalarToDouble(p1.fX);
+ double p1y = SkScalarToDouble(p1.fY);
+
+ double p2x = SkScalarToDouble(p2.fX);
+ double p2y = SkScalarToDouble(p2.fY);
+
+ cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
+ (p1y - p0y) * (p2x - p0x));
+
+ }
+ return cross;
+}
+
+// Returns the first pt with the maximum Y coordinate
+static int find_max_y(const SkPoint pts[], int count) {
+ SkASSERT(count > 0);
+ SkScalar max = pts[0].fY;
+ int firstIndex = 0;
+ for (int i = 1; i < count; ++i) {
+ SkScalar y = pts[i].fY;
+ if (y > max) {
+ max = y;
+ firstIndex = i;
+ }
+ }
+ return firstIndex;
+}
+
+static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
+ int i = index;
+ for (;;) {
+ i = (i + inc) % n;
+ if (i == index) { // we wrapped around, so abort
+ break;
+ }
+ if (pts[index] != pts[i]) { // found a different point, success!
+ break;
+ }
+ }
+ return i;
+}
+
+/**
+ * Starting at index, and moving forward (incrementing), find the xmin and
+ * xmax of the contiguous points that have the same Y.
+ */
+static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
+ int* maxIndexPtr) {
+ const SkScalar y = pts[index].fY;
+ SkScalar min = pts[index].fX;
+ SkScalar max = min;
+ int minIndex = index;
+ int maxIndex = index;
+ for (int i = index + 1; i < n; ++i) {
+ if (pts[i].fY != y) {
+ break;
+ }
+ SkScalar x = pts[i].fX;
+ if (x < min) {
+ min = x;
+ minIndex = i;
+ } else if (x > max) {
+ max = x;
+ maxIndex = i;
+ }
+ }
+ *maxIndexPtr = maxIndex;
+ return minIndex;
+}
+
+static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
+ if (dir) {
+ *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+ }
+}
+
+#if 0
+#include "SkString.h"
+#include "../utils/SkParsePath.h"
+static void dumpPath(const SkPath& path) {
+ SkString str;
+ SkParsePath::ToSVGString(path, &str);
+ SkDebugf("%s\n", str.c_str());
+}
#endif
+
+namespace {
+// for use with convex_dir_test
+double mul(double a, double b) { return a * b; }
+SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
+double toDouble(SkScalar a) { return SkScalarToDouble(a); }
+SkScalar toScalar(SkScalar a) { return a; }
+
+// determines the winding direction of a convex polygon with the precision
+// of T. CAST_SCALAR casts an SkScalar to T.
+template <typename T, T (CAST_SCALAR)(SkScalar)>
+bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
+ // we find the first three points that form a non-degenerate
+ // triangle. If there are no such points then the path is
+ // degenerate. The first is always point 0. Now we find the second
+ // point.
+ int i = 0;
+ enum { kX = 0, kY = 1 };
+ T v0[2];
+ while (1) {
+ v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
+ v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
+ if (v0[kX] || v0[kY]) {
+ break;
+ }
+ if (++i == n - 1) {
+ return false;
+ }
+ }
+ // now find a third point that is not colinear with the first two
+ // points and check the orientation of the triangle (which will be
+ // the same as the orientation of the path).
+ for (++i; i < n; ++i) {
+ T v1[2];
+ v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
+ v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
+ T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
+ if (0 != cross) {
+ *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+ return true;
+ }
+ }
+ return false;
+}
+}
+
+/*
+ * We loop through all contours, and keep the computed cross-product of the
+ * contour that contained the global y-max. If we just look at the first
+ * contour, we may find one that is wound the opposite way (correctly) since
+ * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
+ * that is outer most (or at least has the global y-max) before we can consider
+ * its cross product.
+ */
+bool SkPathRef::cheapComputeDirection(int* dir2) const {
+// dumpPath(*this);
+ // don't want to pay the cost for computing this if it
+ // is unknown, so we don't call isConvex()
+
+ if (SkPath::kUnknown_Direction != fDirection) {
+ *dir2 = static_cast<SkPath::Direction>(fDirection);
+ return true;
+ }
+ const SkPath::Convexity conv = static_cast<SkPath::Convexity>(this->getConvexityOrUnknown());
+ SkPath::Direction tmp;
+
+ ContourIter iter(*this);
+
+ // initialize with our logical y-min
+ SkScalar ymax = this->getBounds().fTop;
+ SkScalar ymaxCross = 0;
+
+ for (; !iter.done(); iter.next()) {
+ int n = iter.count();
+ if (n < 3) {
+ continue;
+ }
+
+ const SkPoint* pts = iter.pts();
+ SkScalar cross = 0;
+ if (SkPath::kConvex_Convexity == conv) {
+ // We try first at scalar precision, and then again at double
+ // precision. This is because the vectors computed between distant
+ // points may lose too much precision.
+ if (convex_dir_test<SkScalar, toScalar>(n, pts, &tmp)) {
+ fDirection = tmp;
+ *dir2 = tmp;
+ return true;
+ }
+ if (convex_dir_test<double, toDouble>(n, pts, &tmp)) {
+ fDirection = tmp;
+ *dir2 = tmp;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ int index = find_max_y(pts, n);
+ if (pts[index].fY < ymax) {
+ continue;
+ }
+
+ // If there is more than 1 distinct point at the y-max, we take the
+ // x-min and x-max of them and just subtract to compute the dir.
+ if (pts[(index + 1) % n].fY == pts[index].fY) {
+ int maxIndex;
+ int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
+ if (minIndex == maxIndex) {
+ goto TRY_CROSSPROD;
+ }
+ SkASSERT(pts[minIndex].fY == pts[index].fY);
+ SkASSERT(pts[maxIndex].fY == pts[index].fY);
+ SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
+ // we just subtract the indices, and let that auto-convert to
+ // SkScalar, since we just want - or + to signal the direction.
+ cross = minIndex - maxIndex;
+ } else {
+ TRY_CROSSPROD:
+ // Find a next and prev index to use for the cross-product test,
+ // but we try to find pts that form non-zero vectors from pts[index]
+ //
+ // Its possible that we can't find two non-degenerate vectors, so
+ // we have to guard our search (e.g. all the pts could be in the
+ // same place).
+
+ // we pass n - 1 instead of -1 so we don't foul up % operator by
+ // passing it a negative LH argument.
+ int prev = find_diff_pt(pts, index, n, n - 1);
+ if (prev == index) {
+ // completely degenerate, skip to next contour
+ continue;
+ }
+ int next = find_diff_pt(pts, index, n, 1);
+ SkASSERT(next != index);
+ cross = cross_prod(pts[prev], pts[index], pts[next]);
+ // if we get a zero and the points are horizontal, then we look at the spread in
+ // x-direction. We really should continue to walk away from the degeneracy until
+ // there is a divergence.
+ if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
+ // construct the subtract so we get the correct Direction below
+ cross = pts[index].fX - pts[next].fX;
+ }
+ }
+
+ if (cross) {
+ // record our best guess so far
+ ymax = pts[index].fY;
+ ymaxCross = cross;
+ }
+ }
+ }
+ if (ymaxCross) {
+ crossToDir(ymaxCross, &tmp);
+ *dir2 = tmp;
+ fDirection = tmp;
+ return true;
+ } else {
+ return false;
+ }
+}
+
« no previous file with comments | « src/core/SkPath.cpp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698