/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkClipStack_DEFINED #define SkClipStack_DEFINED #include "SkDeque.h" #include "SkRegion.h" #include "SkTDArray.h" struct SkRect; class SkPath; // Because a single save/restore state can have multiple clips, this class // stores the stack depth (fSaveCount) and clips (fDeque) separately. // Each clip in fDeque stores the stack state to which it belongs // (i.e., the fSaveCount in force when it was added). Restores are thus // implemented by removing clips from fDeque that have an fSaveCount larger // then the freshly decremented count. class SK_API SkClipStack { public: SkClipStack(); SkClipStack(const SkClipStack& b); explicit SkClipStack(const SkRect& r); explicit SkClipStack(const SkIRect& r); ~SkClipStack(); SkClipStack& operator=(const SkClipStack& b); bool operator==(const SkClipStack& b) const; bool operator!=(const SkClipStack& b) const { return !(*this == b); } void reset(); int getSaveCount() const { return fSaveCount; } void save(); void restore(); enum BoundsType { // The bounding box contains all the pixels that can be written to kNormal_BoundsType, // The bounding box contains all the pixels that cannot be written to. // The real bound extends out to infinity and all the pixels outside // of the bound can be written to. Note that some of the pixels inside // the bound may also be writeable but all pixels that cannot be // written to are guaranteed to be inside. kInsideOut_BoundsType }; /** * getBounds places the current finite bound in its first parameter. In its * second, it indicates which kind of bound is being returned. If * 'canvFiniteBound' is a normal bounding box then it encloses all writeable * pixels. If 'canvFiniteBound' is an inside out bounding box then it * encloses all the un-writeable pixels and the true/normal bound is the * infinite plane. isIntersectionOfRects is an optional parameter * that is true if 'canvFiniteBound' resulted from an intersection of rects. */ void getBounds(SkRect* canvFiniteBound, BoundsType* boundType, bool* isIntersectionOfRects = NULL) const; /** * Takes an input rect in device space and conservatively clips it to the * clip-stack. If false is returned then the rect does not intersect the * clip and is unmodified. */ bool intersectRectWithClip(SkRect* devRect) const; void clipDevRect(const SkIRect& ir, SkRegion::Op op) { SkRect r; r.set(ir); this->clipDevRect(r, op, false); } void clipDevRect(const SkRect&, SkRegion::Op, bool doAA); void clipDevPath(const SkPath&, SkRegion::Op, bool doAA); // An optimized version of clipDevRect(emptyRect, kIntersect, ...) void clipEmpty(); /** * isWideOpen returns true if the clip state corresponds to the infinite * plane (i.e., draws are not limited at all) */ bool isWideOpen() const; /** * Add a callback function that will be called whenever a clip state * is no longer viable. This will occur whenever restore * is called or when a clipDevRect or clipDevPath call updates the * clip within an existing save/restore state. Each clip state is * represented by a unique generation ID. */ typedef void (*PFPurgeClipCB)(int genID, void* data); void addPurgeClipCallback(PFPurgeClipCB callback, void* data) const; /** * Remove a callback added earlier via addPurgeClipCallback */ void removePurgeClipCallback(PFPurgeClipCB callback, void* data) const; /** * The generation ID has three reserved values to indicate special * (potentially ignoreable) cases */ static const int32_t kInvalidGenID = 0; static const int32_t kEmptyGenID = 1; // no pixels writeable static const int32_t kWideOpenGenID = 2; // all pixels writeable int32_t getTopmostGenID() const; private: struct Rec; public: class Iter { public: enum IterStart { kBottom_IterStart = SkDeque::Iter::kFront_IterStart, kTop_IterStart = SkDeque::Iter::kBack_IterStart }; /** * Creates an uninitialized iterator. Must be reset() */ Iter(); Iter(const SkClipStack& stack, IterStart startLoc); struct Clip { Clip() : fRect(NULL), fPath(NULL), fOp(SkRegion::kIntersect_Op), fDoAA(false) {} friend bool operator==(const Clip& a, const Clip& b); friend bool operator!=(const Clip& a, const Clip& b); /** * Gets the bounds of the clip element, either the rect or path bounds. */ const SkRect& getBounds() const; const SkRect* fRect; // if non-null, this is a rect clip const SkPath* fPath; // if non-null, this is a path clip SkRegion::Op fOp; bool fDoAA; int32_t fGenID; }; /** * Return the clip for this element in the iterator. If next() returns * NULL, then the iterator is done. The type of clip is determined by * the pointers fRect and fPath: * * fRect==NULL fPath!=NULL path clip * fRect!=NULL fPath==NULL rect clip * fRect==NULL fPath==NULL empty clip */ const Clip* next(); const Clip* prev(); /** * Moves the iterator to the topmost clip with the specified RegionOp * and returns that clip. If no clip with that op is found, * returns NULL. */ const Clip* skipToTopmost(SkRegion::Op op); /** * Restarts the iterator on a clip stack. */ void reset(const SkClipStack& stack, IterStart startLoc); private: const SkClipStack* fStack; Clip fClip; SkDeque::Iter fIter; /** * updateClip updates fClip to the current state of fIter. It unifies * functionality needed by both next() and prev(). */ const Clip* updateClip(const SkClipStack::Rec* rec); }; /** * The B2TIter iterates from the bottom of the stack to the top. * It inherits privately from Iter to prevent access to reverse iteration. */ class B2TIter : private Iter { public: B2TIter() {} /** * Wrap Iter's 2 parameter ctor to force initialization to the * beginning of the deque/bottom of the stack */ B2TIter(const SkClipStack& stack) : INHERITED(stack, kBottom_IterStart) { } using Iter::Clip; using Iter::next; /** * Wrap Iter::reset to force initialization to the * beginning of the deque/bottom of the stack */ void reset(const SkClipStack& stack) { this->INHERITED::reset(stack, kBottom_IterStart); } private: typedef Iter INHERITED; }; /** * GetConservativeBounds returns a conservative bound of the current clip. * Since this could be the infinite plane (if inverse fills were involved) the * maxWidth and maxHeight parameters can be used to limit the returned bound * to the expected drawing area. Similarly, the offsetX and offsetY parameters * allow the caller to offset the returned bound to account for translated * drawing areas (i.e., those resulting from a saveLayer). For finite bounds, * the translation (+offsetX, +offsetY) is applied before the clamp to the * maximum rectangle: [0,maxWidth) x [0,maxHeight). * isIntersectionOfRects is an optional parameter that is true when * 'devBounds' is the result of an intersection of rects. In this case * 'devBounds' is the exact answer/clip. */ void getConservativeBounds(int offsetX, int offsetY, int maxWidth, int maxHeight, SkRect* devBounds, bool* isIntersectionOfRects = NULL) const; private: friend class Iter; SkDeque fDeque; int fSaveCount; // Generation ID for the clip stack. This is incremented for each // clipDevRect and clipDevPath call. 0 is reserved to indicate an // invalid ID. static int32_t gGenID; struct ClipCallbackData { PFPurgeClipCB fCallback; void* fData; friend bool operator==(const ClipCallbackData& a, const ClipCallbackData& b) { return a.fCallback == b.fCallback && a.fData == b.fData; } }; mutable SkTDArray fCallbackData; /** * Invoke all the purge callbacks passing in rec's generation ID. */ void purgeClip(Rec* rec); /** * Return the next unique generation ID. */ static int32_t GetNextGenID(); }; #endif