| 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;
|
| + }
|
| +}
|
| +
|
|
|