michael@0: michael@0: /* michael@0: * Copyright 2012 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #ifndef SkPathRef_DEFINED michael@0: #define SkPathRef_DEFINED michael@0: michael@0: #include "SkMatrix.h" michael@0: #include "SkPoint.h" michael@0: #include "SkRect.h" michael@0: #include "SkRefCnt.h" michael@0: #include "SkTDArray.h" michael@0: #include // ptrdiff_t michael@0: michael@0: class SkRBuffer; michael@0: class SkWBuffer; michael@0: michael@0: /** michael@0: * Holds the path verbs and points. It is versioned by a generation ID. None of its public methods michael@0: * modify the contents. To modify or append to the verbs/points wrap the SkPathRef in an michael@0: * SkPathRef::Editor object. Installing the editor resets the generation ID. It also performs michael@0: * copy-on-write if the SkPathRef is shared by multiple SkPaths. The caller passes the Editor's michael@0: * constructor a SkAutoTUnref, which may be updated to point to a new SkPathRef after the editor's michael@0: * constructor returns. michael@0: * michael@0: * The points and verbs are stored in a single allocation. The points are at the begining of the michael@0: * allocation while the verbs are stored at end of the allocation, in reverse order. Thus the points michael@0: * and verbs both grow into the middle of the allocation until the meet. To access verb i in the michael@0: * verb array use ref.verbs()[~i] (because verbs() returns a pointer just beyond the first michael@0: * logical verb or the last verb in memory). michael@0: */ michael@0: michael@0: class SK_API SkPathRef : public ::SkRefCnt { michael@0: public: michael@0: SK_DECLARE_INST_COUNT(SkPathRef); michael@0: michael@0: class Editor { michael@0: public: michael@0: Editor(SkAutoTUnref* pathRef, michael@0: int incReserveVerbs = 0, michael@0: int incReservePoints = 0); michael@0: michael@0: ~Editor() { SkDEBUGCODE(sk_atomic_dec(&fPathRef->fEditorsAttached);) } michael@0: michael@0: /** michael@0: * Returns the array of points. michael@0: */ michael@0: SkPoint* points() { return fPathRef->getPoints(); } michael@0: const SkPoint* points() const { return fPathRef->points(); } michael@0: michael@0: /** michael@0: * Gets the ith point. Shortcut for this->points() + i michael@0: */ michael@0: SkPoint* atPoint(int i) { michael@0: SkASSERT((unsigned) i < (unsigned) fPathRef->fPointCnt); michael@0: return this->points() + i; michael@0: }; michael@0: const SkPoint* atPoint(int i) const { michael@0: SkASSERT((unsigned) i < (unsigned) fPathRef->fPointCnt); michael@0: return this->points() + i; michael@0: }; michael@0: michael@0: /** michael@0: * Adds the verb and allocates space for the number of points indicated by the verb. The michael@0: * return value is a pointer to where the points for the verb should be written. michael@0: * 'weight' is only used if 'verb' is kConic_Verb michael@0: */ michael@0: SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight = 0) { michael@0: SkDEBUGCODE(fPathRef->validate();) michael@0: return fPathRef->growForVerb(verb, weight); michael@0: } michael@0: michael@0: /** michael@0: * Allocates space for multiple instances of a particular verb and the michael@0: * requisite points & weights. michael@0: * The return pointer points at the first new point (indexed normally []). michael@0: * If 'verb' is kConic_Verb, 'weights' will return a pointer to the michael@0: * space for the conic weights (indexed normally). michael@0: */ michael@0: SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, michael@0: int numVbs, michael@0: SkScalar** weights = NULL) { michael@0: return fPathRef->growForRepeatedVerb(verb, numVbs, weights); michael@0: } michael@0: michael@0: /** michael@0: * Resets the path ref to a new verb and point count. The new verbs and points are michael@0: * uninitialized. michael@0: */ michael@0: void resetToSize(int newVerbCnt, int newPointCnt, int newConicCount) { michael@0: fPathRef->resetToSize(newVerbCnt, newPointCnt, newConicCount); michael@0: } michael@0: michael@0: /** michael@0: * Gets the path ref that is wrapped in the Editor. michael@0: */ michael@0: SkPathRef* pathRef() { return fPathRef; } michael@0: michael@0: void setIsOval(bool isOval) { fPathRef->setIsOval(isOval); } michael@0: michael@0: void setBounds(const SkRect& rect) { fPathRef->setBounds(rect); } michael@0: michael@0: private: michael@0: SkPathRef* fPathRef; michael@0: }; michael@0: michael@0: public: michael@0: /** michael@0: * Gets a path ref with no verbs or points. michael@0: */ michael@0: static SkPathRef* CreateEmpty(); michael@0: michael@0: /** michael@0: * Returns true if all of the points in this path are finite, meaning there michael@0: * are no infinities and no NaNs. michael@0: */ michael@0: bool isFinite() const { michael@0: if (fBoundsIsDirty) { michael@0: this->computeBounds(); michael@0: } michael@0: return SkToBool(fIsFinite); michael@0: } michael@0: michael@0: /** michael@0: * Returns a mask, where each bit corresponding to a SegmentMask is michael@0: * set if the path contains 1 or more segments of that type. michael@0: * Returns 0 for an empty path (no segments). michael@0: */ michael@0: uint32_t getSegmentMasks() const { return fSegmentMask; } michael@0: michael@0: /** Returns true if the path is an oval. michael@0: * michael@0: * @param rect returns the bounding rect of this oval. It's a circle michael@0: * if the height and width are the same. michael@0: * michael@0: * @return true if this path is an oval. michael@0: * Tracking whether a path is an oval is considered an michael@0: * optimization for performance and so some paths that are in michael@0: * fact ovals can report false. michael@0: */ michael@0: bool isOval(SkRect* rect) const { michael@0: if (fIsOval && NULL != rect) { michael@0: *rect = getBounds(); michael@0: } michael@0: michael@0: return SkToBool(fIsOval); michael@0: } michael@0: michael@0: bool hasComputedBounds() const { michael@0: return !fBoundsIsDirty; michael@0: } michael@0: michael@0: /** Returns the bounds of the path's points. If the path contains 0 or 1 michael@0: points, the bounds is set to (0,0,0,0), and isEmpty() will return true. michael@0: Note: this bounds may be larger than the actual shape, since curves michael@0: do not extend as far as their control points. michael@0: */ michael@0: const SkRect& getBounds() const { michael@0: if (fBoundsIsDirty) { michael@0: this->computeBounds(); michael@0: } michael@0: return fBounds; michael@0: } michael@0: michael@0: /** michael@0: * Transforms a path ref by a matrix, allocating a new one only if necessary. michael@0: */ michael@0: static void CreateTransformedCopy(SkAutoTUnref* dst, michael@0: const SkPathRef& src, michael@0: const SkMatrix& matrix); michael@0: michael@0: static SkPathRef* CreateFromBuffer(SkRBuffer* buffer); michael@0: michael@0: /** michael@0: * Rollsback a path ref to zero verbs and points with the assumption that the path ref will be michael@0: * repopulated with approximately the same number of verbs and points. A new path ref is created michael@0: * only if necessary. michael@0: */ michael@0: static void Rewind(SkAutoTUnref* pathRef); michael@0: michael@0: virtual ~SkPathRef() { michael@0: SkDEBUGCODE(this->validate();) michael@0: sk_free(fPoints); michael@0: michael@0: SkDEBUGCODE(fPoints = NULL;) michael@0: SkDEBUGCODE(fVerbs = NULL;) michael@0: SkDEBUGCODE(fVerbCnt = 0x9999999;) michael@0: SkDEBUGCODE(fPointCnt = 0xAAAAAAA;) michael@0: SkDEBUGCODE(fPointCnt = 0xBBBBBBB;) michael@0: SkDEBUGCODE(fGenerationID = 0xEEEEEEEE;) michael@0: SkDEBUGCODE(fEditorsAttached = 0x7777777;) michael@0: } michael@0: michael@0: int countPoints() const { SkDEBUGCODE(this->validate();) return fPointCnt; } michael@0: int countVerbs() const { SkDEBUGCODE(this->validate();) return fVerbCnt; } michael@0: int countWeights() const { SkDEBUGCODE(this->validate();) return fConicWeights.count(); } michael@0: michael@0: /** michael@0: * Returns a pointer one beyond the first logical verb (last verb in memory order). michael@0: */ michael@0: const uint8_t* verbs() const { SkDEBUGCODE(this->validate();) return fVerbs; } michael@0: michael@0: /** michael@0: * Returns a const pointer to the first verb in memory (which is the last logical verb). michael@0: */ michael@0: const uint8_t* verbsMemBegin() const { return this->verbs() - fVerbCnt; } michael@0: michael@0: /** michael@0: * Returns a const pointer to the first point. michael@0: */ michael@0: const SkPoint* points() const { SkDEBUGCODE(this->validate();) return fPoints; } michael@0: michael@0: /** michael@0: * Shortcut for this->points() + this->countPoints() michael@0: */ michael@0: const SkPoint* pointsEnd() const { return this->points() + this->countPoints(); } michael@0: michael@0: const SkScalar* conicWeights() const { SkDEBUGCODE(this->validate();) return fConicWeights.begin(); } michael@0: const SkScalar* conicWeightsEnd() const { SkDEBUGCODE(this->validate();) return fConicWeights.end(); } michael@0: michael@0: /** michael@0: * Convenience methods for getting to a verb or point by index. michael@0: */ michael@0: uint8_t atVerb(int index) const { michael@0: SkASSERT((unsigned) index < (unsigned) fVerbCnt); michael@0: return this->verbs()[~index]; michael@0: } michael@0: const SkPoint& atPoint(int index) const { michael@0: SkASSERT((unsigned) index < (unsigned) fPointCnt); michael@0: return this->points()[index]; michael@0: } michael@0: michael@0: bool operator== (const SkPathRef& ref) const; michael@0: michael@0: /** michael@0: * Writes the path points and verbs to a buffer. michael@0: */ michael@0: void writeToBuffer(SkWBuffer* buffer) const; michael@0: michael@0: /** michael@0: * Gets the number of bytes that would be written in writeBuffer() michael@0: */ michael@0: uint32_t writeSize() const; michael@0: michael@0: /** michael@0: * Gets an ID that uniquely identifies the contents of the path ref. If two path refs have the michael@0: * same ID then they have the same verbs and points. However, two path refs may have the same michael@0: * contents but different genIDs. michael@0: */ michael@0: uint32_t genID() const; michael@0: michael@0: private: michael@0: enum SerializationOffsets { michael@0: kIsFinite_SerializationShift = 25, // requires 1 bit michael@0: kIsOval_SerializationShift = 24, // requires 1 bit michael@0: kSegmentMask_SerializationShift = 0 // requires 4 bits michael@0: }; michael@0: michael@0: SkPathRef() { michael@0: fBoundsIsDirty = true; // this also invalidates fIsFinite michael@0: fPointCnt = 0; michael@0: fVerbCnt = 0; michael@0: fVerbs = NULL; michael@0: fPoints = NULL; michael@0: fFreeSpace = 0; michael@0: fGenerationID = kEmptyGenID; michael@0: fSegmentMask = 0; michael@0: fIsOval = false; michael@0: SkDEBUGCODE(fEditorsAttached = 0;) michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: void copy(const SkPathRef& ref, int additionalReserveVerbs, int additionalReservePoints); michael@0: michael@0: // Return true if the computed bounds are finite. michael@0: static bool ComputePtBounds(SkRect* bounds, const SkPathRef& ref) { michael@0: int count = ref.countPoints(); michael@0: if (count <= 1) { // we ignore just 1 point (moveto) michael@0: bounds->setEmpty(); michael@0: return count ? ref.points()->isFinite() : true; michael@0: } else { michael@0: return bounds->setBoundsCheck(ref.points(), count); michael@0: } michael@0: } michael@0: michael@0: // called, if dirty, by getBounds() michael@0: void computeBounds() const { michael@0: SkDEBUGCODE(this->validate();) michael@0: SkASSERT(fBoundsIsDirty); michael@0: michael@0: fIsFinite = ComputePtBounds(&fBounds, *this); michael@0: fBoundsIsDirty = false; michael@0: } michael@0: michael@0: void setBounds(const SkRect& rect) { michael@0: SkASSERT(rect.fLeft <= rect.fRight && rect.fTop <= rect.fBottom); michael@0: fBounds = rect; michael@0: fBoundsIsDirty = false; michael@0: fIsFinite = fBounds.isFinite(); michael@0: } michael@0: michael@0: /** Makes additional room but does not change the counts or change the genID */ michael@0: void incReserve(int additionalVerbs, int additionalPoints) { michael@0: SkDEBUGCODE(this->validate();) michael@0: size_t space = additionalVerbs * sizeof(uint8_t) + additionalPoints * sizeof (SkPoint); michael@0: this->makeSpace(space); michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** Resets the path ref with verbCount verbs and pointCount points, all uninitialized. Also michael@0: * allocates space for reserveVerb additional verbs and reservePoints additional points.*/ michael@0: void resetToSize(int verbCount, int pointCount, int conicCount, michael@0: int reserveVerbs = 0, int reservePoints = 0) { michael@0: SkDEBUGCODE(this->validate();) michael@0: fBoundsIsDirty = true; // this also invalidates fIsFinite michael@0: fGenerationID = 0; michael@0: michael@0: fSegmentMask = 0; michael@0: fIsOval = false; michael@0: michael@0: size_t newSize = sizeof(uint8_t) * verbCount + sizeof(SkPoint) * pointCount; michael@0: size_t newReserve = sizeof(uint8_t) * reserveVerbs + sizeof(SkPoint) * reservePoints; michael@0: size_t minSize = newSize + newReserve; michael@0: michael@0: ptrdiff_t sizeDelta = this->currSize() - minSize; michael@0: michael@0: if (sizeDelta < 0 || static_cast(sizeDelta) >= 3 * minSize) { michael@0: sk_free(fPoints); michael@0: fPoints = NULL; michael@0: fVerbs = NULL; michael@0: fFreeSpace = 0; michael@0: fVerbCnt = 0; michael@0: fPointCnt = 0; michael@0: this->makeSpace(minSize); michael@0: fVerbCnt = verbCount; michael@0: fPointCnt = pointCount; michael@0: fFreeSpace -= newSize; michael@0: } else { michael@0: fPointCnt = pointCount; michael@0: fVerbCnt = verbCount; michael@0: fFreeSpace = this->currSize() - minSize; michael@0: } michael@0: fConicWeights.setCount(conicCount); michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** michael@0: * Increases the verb count by numVbs and point count by the required amount. michael@0: * The new points are uninitialized. All the new verbs are set to the specified michael@0: * verb. If 'verb' is kConic_Verb, 'weights' will return a pointer to the michael@0: * uninitialized conic weights. michael@0: */ michael@0: SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, int numVbs, SkScalar** weights); michael@0: michael@0: /** michael@0: * Increases the verb count 1, records the new verb, and creates room for the requisite number michael@0: * of additional points. A pointer to the first point is returned. Any new points are michael@0: * uninitialized. michael@0: */ michael@0: SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight); michael@0: michael@0: /** michael@0: * Ensures that the free space available in the path ref is >= size. The verb and point counts michael@0: * are not changed. michael@0: */ michael@0: void makeSpace(size_t size) { michael@0: SkDEBUGCODE(this->validate();) michael@0: ptrdiff_t growSize = size - fFreeSpace; michael@0: if (growSize <= 0) { michael@0: return; michael@0: } michael@0: size_t oldSize = this->currSize(); michael@0: // round to next multiple of 8 bytes michael@0: growSize = (growSize + 7) & ~static_cast(7); michael@0: // we always at least double the allocation michael@0: if (static_cast(growSize) < oldSize) { michael@0: growSize = oldSize; michael@0: } michael@0: if (growSize < kMinSize) { michael@0: growSize = kMinSize; michael@0: } michael@0: size_t newSize = oldSize + growSize; michael@0: // Note that realloc could memcpy more than we need. It seems to be a win anyway. TODO: michael@0: // encapsulate this. michael@0: fPoints = reinterpret_cast(sk_realloc_throw(fPoints, newSize)); michael@0: size_t oldVerbSize = fVerbCnt * sizeof(uint8_t); michael@0: void* newVerbsDst = reinterpret_cast( michael@0: reinterpret_cast(fPoints) + newSize - oldVerbSize); michael@0: void* oldVerbsSrc = reinterpret_cast( michael@0: reinterpret_cast(fPoints) + oldSize - oldVerbSize); michael@0: memmove(newVerbsDst, oldVerbsSrc, oldVerbSize); michael@0: fVerbs = reinterpret_cast(reinterpret_cast(fPoints) + newSize); michael@0: fFreeSpace += growSize; michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** michael@0: * Private, non-const-ptr version of the public function verbsMemBegin(). michael@0: */ michael@0: uint8_t* verbsMemWritable() { michael@0: SkDEBUGCODE(this->validate();) michael@0: return fVerbs - fVerbCnt; michael@0: } michael@0: michael@0: /** michael@0: * Gets the total amount of space allocated for verbs, points, and reserve. michael@0: */ michael@0: size_t currSize() const { michael@0: return reinterpret_cast(fVerbs) - reinterpret_cast(fPoints); michael@0: } michael@0: michael@0: SkDEBUGCODE(void validate() const;) michael@0: michael@0: /** michael@0: * Called the first time someone calls CreateEmpty to actually create the singleton. michael@0: */ michael@0: static void CreateEmptyImpl(int/*unused*/); michael@0: michael@0: void setIsOval(bool isOval) { fIsOval = isOval; } michael@0: michael@0: SkPoint* getPoints() { michael@0: SkDEBUGCODE(this->validate();) michael@0: fIsOval = false; michael@0: return fPoints; michael@0: } michael@0: michael@0: enum { michael@0: kMinSize = 256, michael@0: }; michael@0: michael@0: mutable SkRect fBounds; michael@0: uint8_t fSegmentMask; michael@0: mutable uint8_t fBoundsIsDirty; michael@0: mutable SkBool8 fIsFinite; // only meaningful if bounds are valid michael@0: mutable SkBool8 fIsOval; michael@0: michael@0: SkPoint* fPoints; // points to begining of the allocation michael@0: uint8_t* fVerbs; // points just past the end of the allocation (verbs grow backwards) michael@0: int fVerbCnt; michael@0: int fPointCnt; michael@0: size_t fFreeSpace; // redundant but saves computation michael@0: SkTDArray fConicWeights; michael@0: michael@0: enum { michael@0: kEmptyGenID = 1, // GenID reserved for path ref with zero points and zero verbs. michael@0: }; michael@0: mutable uint32_t fGenerationID; michael@0: SkDEBUGCODE(int32_t fEditorsAttached;) // assert that only one editor in use at any time. michael@0: michael@0: friend class PathRefTest_Private; michael@0: typedef SkRefCnt INHERITED; michael@0: }; michael@0: michael@0: #endif