• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkClipStack_DEFINED
9 #define SkClipStack_DEFINED
10 
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkRRect.h"
14 #include "include/core/SkRect.h"
15 #include "include/core/SkRegion.h"
16 #include "include/core/SkShader.h"
17 #include "include/private/base/SkDeque.h"
18 #include "src/base/SkTLazy.h"
19 #include "src/core/SkMessageBus.h"
20 
21 // Because a single save/restore state can have multiple clips, this class
22 // stores the stack depth (fSaveCount) and clips (fDeque) separately.
23 // Each clip in fDeque stores the stack state to which it belongs
24 // (i.e., the fSaveCount in force when it was added). Restores are thus
25 // implemented by removing clips from fDeque that have an fSaveCount larger
26 // then the freshly decremented count.
27 class SkClipStack {
28 public:
29     enum BoundsType {
30         // The bounding box contains all the pixels that can be written to
31         kNormal_BoundsType,
32         // The bounding box contains all the pixels that cannot be written to.
33         // The real bound extends out to infinity and all the pixels outside
34         // of the bound can be written to. Note that some of the pixels inside
35         // the bound may also be writeable but all pixels that cannot be
36         // written to are guaranteed to be inside.
37         kInsideOut_BoundsType
38     };
39 
40     /**
41      * An element of the clip stack. It represents a shape combined with the prevoius clip using a
42      * set operator. Each element can be antialiased or not.
43      */
44     class Element {
45     public:
46         /** This indicates the shape type of the clip element in device space. */
47         enum class DeviceSpaceType {
48             //!< This element makes the clip empty (regardless of previous elements).
49             kEmpty,
50             //!< This element combines a device space rect with the current clip.
51             kRect,
52             //!< This element combines a device space round-rect with the current clip.
53             kRRect,
54             //!< This element combines a device space path with the current clip.
55             kPath,
56             //!< This element does not have geometry, but applies a shader to the clip
57             kShader,
58 
59             kLastType = kShader
60         };
61         static const int kTypeCnt = (int)DeviceSpaceType::kLastType + 1;
62 
Element()63         Element() {
64             this->initCommon(0, SkClipOp::kIntersect, false);
65             this->setEmpty();
66         }
67 
68         Element(const Element&);
69 
Element(const SkRect & rect,const SkMatrix & m,SkClipOp op,bool doAA)70         Element(const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
71             this->initRect(0, rect, m, op, doAA);
72         }
73 
Element(const SkRRect & rrect,const SkMatrix & m,SkClipOp op,bool doAA)74         Element(const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
75             this->initRRect(0, rrect, m, op, doAA);
76         }
77 
Element(const SkPath & path,const SkMatrix & m,SkClipOp op,bool doAA)78         Element(const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
79             this->initPath(0, path, m, op, doAA);
80         }
81 
Element(sk_sp<SkShader> shader)82         Element(sk_sp<SkShader> shader) {
83             this->initShader(0, std::move(shader));
84         }
85 
Element(const SkRect & rect,bool doAA)86         Element(const SkRect& rect, bool doAA) {
87             this->initReplaceRect(0, rect, doAA);
88         }
89 
90         ~Element();
91 
92         bool operator== (const Element& element) const;
93         bool operator!= (const Element& element) const { return !(*this == element); }
94 
95         //!< Call to get the type of the clip element.
getDeviceSpaceType()96         DeviceSpaceType getDeviceSpaceType() const { return fDeviceSpaceType; }
97 
98         //!< Call to get the save count associated with this clip element.
getSaveCount()99         int getSaveCount() const { return fSaveCount; }
100 
101         //!< Call if getDeviceSpaceType() is kPath to get the path.
getDeviceSpacePath()102         const SkPath& getDeviceSpacePath() const {
103             SkASSERT(DeviceSpaceType::kPath == fDeviceSpaceType);
104             return *fDeviceSpacePath;
105         }
106 
107         //!< Call if getDeviceSpaceType() is kRRect to get the round-rect.
getDeviceSpaceRRect()108         const SkRRect& getDeviceSpaceRRect() const {
109             SkASSERT(DeviceSpaceType::kRRect == fDeviceSpaceType);
110             return fDeviceSpaceRRect;
111         }
112 
113         //!< Call if getDeviceSpaceType() is kRect to get the rect.
getDeviceSpaceRect()114         const SkRect& getDeviceSpaceRect() const {
115             SkASSERT(DeviceSpaceType::kRect == fDeviceSpaceType &&
116                      (fDeviceSpaceRRect.isRect() || fDeviceSpaceRRect.isEmpty()));
117             return fDeviceSpaceRRect.getBounds();
118         }
119 
120         //!<Call if getDeviceSpaceType() is kShader to get a reference to the clip shader.
refShader()121         sk_sp<SkShader> refShader() const {
122             return fShader;
123         }
getShader()124         const SkShader* getShader() const {
125             return fShader.get();
126         }
127 
128         //!< Call if getDeviceSpaceType() is not kEmpty to get the set operation used to combine
129         //!< this element.
getOp()130         SkClipOp getOp() const { return fOp; }
131         // Augments getOps()'s behavior by requiring a clip reset before the op is applied.
isReplaceOp()132         bool isReplaceOp() const { return fIsReplace; }
133 
134         //!< Call to get the element as a path, regardless of its type.
135         void asDeviceSpacePath(SkPath* path) const;
136 
137         //!< Call if getType() is not kPath to get the element as a round rect.
asDeviceSpaceRRect()138         const SkRRect& asDeviceSpaceRRect() const {
139             SkASSERT(DeviceSpaceType::kPath != fDeviceSpaceType);
140             return fDeviceSpaceRRect;
141         }
142 
143         /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased
144             when it is rasterized. */
isAA()145         bool isAA() const { return fDoAA; }
146 
147         //!< Inverts the fill of the clip shape. Note that a kEmpty element remains kEmpty.
148         void invertShapeFillType();
149 
150         /** The GenID can be used by clip stack clients to cache representations of the clip. The
151             ID corresponds to the set of clip elements up to and including this element within the
152             stack not to the element itself. That is the same clip path in different stacks will
153             have a different ID since the elements produce different clip result in the context of
154             their stacks. */
getGenID()155         uint32_t getGenID() const { SkASSERT(kInvalidGenID != fGenID); return fGenID; }
156 
157         /**
158          * Gets the bounds of the clip element, either the rect or path bounds. (Whether the shape
159          * is inverse filled is not considered.)
160          */
161         const SkRect& getBounds() const;
162 
163         /**
164          * Conservatively checks whether the clip shape contains the rect/rrect. (Whether the shape
165          * is inverse filled is not considered.)
166          */
167         bool contains(const SkRect& rect) const;
168         bool contains(const SkRRect& rrect) const;
169 
170         /**
171          * Is the clip shape inverse filled.
172          */
isInverseFilled()173         bool isInverseFilled() const {
174             return DeviceSpaceType::kPath == fDeviceSpaceType &&
175                    fDeviceSpacePath->isInverseFillType();
176         }
177 
178 #ifdef SK_DEBUG
179         /**
180          * Dumps the element to SkDebugf. This is intended for Skia development debugging
181          * Don't rely on the existence of this function or the formatting of its output.
182          */
183         void dump() const;
184 #endif
185 
186     private:
187         friend class SkClipStack;
188 
189         SkTLazy<SkPath> fDeviceSpacePath;
190         SkRRect fDeviceSpaceRRect;
191         sk_sp<SkShader> fShader;
192         int fSaveCount;  // save count of stack when this element was added.
193         SkClipOp fOp;
194         DeviceSpaceType fDeviceSpaceType;
195         bool fDoAA;
196         bool fIsReplace;
197 
198         /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's
199            bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the
200            conservative bounding box of the pixels that aren't clipped (i.e., any pixels that can be
201            drawn to are inside the bound). When fFiniteBoundType is kInsideOut_BoundsType (which
202            occurs when a clip is inverse filled), fFiniteBound represents the conservative bounding
203            box of the pixels that _are_ clipped (i.e., any pixels that cannot be drawn to are inside
204            the bound). When fFiniteBoundType is kInsideOut_BoundsType the actual bound is the
205            infinite plane. This behavior of fFiniteBoundType and fFiniteBound is required so that we
206            can capture the cancelling out of the extensions to infinity when two inverse filled
207            clips are Booleaned together. */
208         SkClipStack::BoundsType fFiniteBoundType;
209         SkRect fFiniteBound;
210 
211         // When element is applied to the previous elements in the stack is the result known to be
212         // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle.
213         bool fIsIntersectionOfRects;
214 
215         uint32_t fGenID;
216 
Element(int saveCount)217         Element(int saveCount) {
218             this->initCommon(saveCount, SkClipOp::kIntersect, false);
219             this->setEmpty();
220         }
221 
Element(int saveCount,const SkRRect & rrect,const SkMatrix & m,SkClipOp op,bool doAA)222         Element(int saveCount, const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
223             this->initRRect(saveCount, rrect, m, op, doAA);
224         }
225 
Element(int saveCount,const SkRect & rect,const SkMatrix & m,SkClipOp op,bool doAA)226         Element(int saveCount, const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
227             this->initRect(saveCount, rect, m, op, doAA);
228         }
229 
Element(int saveCount,const SkPath & path,const SkMatrix & m,SkClipOp op,bool doAA)230         Element(int saveCount, const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
231             this->initPath(saveCount, path, m, op, doAA);
232         }
233 
Element(int saveCount,sk_sp<SkShader> shader)234         Element(int saveCount, sk_sp<SkShader> shader) {
235             this->initShader(saveCount, std::move(shader));
236         }
237 
Element(int saveCount,const SkRect & rect,bool doAA)238         Element(int saveCount, const SkRect& rect, bool doAA) {
239             this->initReplaceRect(saveCount, rect, doAA);
240         }
241 
242         void initCommon(int saveCount, SkClipOp op, bool doAA);
243         void initRect(int saveCount, const SkRect&, const SkMatrix&, SkClipOp, bool doAA);
244         void initRRect(int saveCount, const SkRRect&, const SkMatrix&, SkClipOp, bool doAA);
245         void initPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA);
246         void initAsPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA);
247         void initShader(int saveCount, sk_sp<SkShader>);
248         void initReplaceRect(int saveCount, const SkRect&, bool doAA);
249 
250         void setEmpty();
251 
252         // All Element methods below are only used within SkClipStack.cpp
253         inline void checkEmpty() const;
254         inline bool canBeIntersectedInPlace(int saveCount, SkClipOp op) const;
255         /* This method checks to see if two rect clips can be safely merged into one. The issue here
256           is that to be strictly correct all the edges of the resulting rect must have the same
257           anti-aliasing. */
258         bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const;
259         /** Determines possible finite bounds for the Element given the previous element of the
260             stack */
261         void updateBoundAndGenID(const Element* prior);
262         // The different combination of fill & inverse fill when combining bounding boxes
263         enum FillCombo {
264             kPrev_Cur_FillCombo,
265             kPrev_InvCur_FillCombo,
266             kInvPrev_Cur_FillCombo,
267             kInvPrev_InvCur_FillCombo
268         };
269         // per-set operation functions used by updateBoundAndGenID().
270         inline void combineBoundsDiff(FillCombo combination, const SkRect& prevFinite);
271         inline void combineBoundsIntersection(int combination, const SkRect& prevFinite);
272     };
273 
274     SkClipStack();
275     SkClipStack(void* storage, size_t size);
276     SkClipStack(const SkClipStack& b);
277     ~SkClipStack();
278 
279     SkClipStack& operator=(const SkClipStack& b);
280     bool operator==(const SkClipStack& b) const;
281     bool operator!=(const SkClipStack& b) const { return !(*this == b); }
282 
283     void reset();
284 
getSaveCount()285     int getSaveCount() const { return fSaveCount; }
286     void save();
287     void restore();
288 
289     class AutoRestore {
290     public:
AutoRestore(SkClipStack * cs,bool doSave)291         AutoRestore(SkClipStack* cs, bool doSave)
292             : fCS(cs), fSaveCount(cs->getSaveCount())
293         {
294             if (doSave) {
295                 fCS->save();
296             }
297         }
~AutoRestore()298         ~AutoRestore() {
299             SkASSERT(fCS->getSaveCount() >= fSaveCount);  // no underflow
300             while (fCS->getSaveCount() > fSaveCount) {
301                 fCS->restore();
302             }
303         }
304 
305     private:
306         SkClipStack* fCS;
307         const int    fSaveCount;
308     };
309 
310     /**
311      * getBounds places the current finite bound in its first parameter. In its
312      * second, it indicates which kind of bound is being returned. If
313      * 'canvFiniteBound' is a normal bounding box then it encloses all writeable
314      * pixels. If 'canvFiniteBound' is an inside out bounding box then it
315      * encloses all the un-writeable pixels and the true/normal bound is the
316      * infinite plane. isIntersectionOfRects is an optional parameter
317      * that is true if 'canvFiniteBound' resulted from an intersection of rects.
318      */
319     void getBounds(SkRect* canvFiniteBound,
320                    BoundsType* boundType,
321                    bool* isIntersectionOfRects = nullptr) const;
322 
323     SkRect bounds(const SkIRect& deviceBounds) const;
324     bool isEmpty(const SkIRect& deviceBounds) const;
325 
326     /**
327      * Returns true if the input (r)rect in device space is entirely contained
328      * by the clip. A return value of false does not guarantee that the (r)rect
329      * is not contained by the clip.
330      */
quickContains(const SkRect & devRect)331     bool quickContains(const SkRect& devRect) const {
332         return this->isWideOpen() || this->internalQuickContains(devRect);
333     }
334 
quickContains(const SkRRect & devRRect)335     bool quickContains(const SkRRect& devRRect) const {
336         return this->isWideOpen() || this->internalQuickContains(devRRect);
337     }
338 
clipDevRect(const SkIRect & ir,SkClipOp op)339     void clipDevRect(const SkIRect& ir, SkClipOp op) {
340         SkRect r;
341         r.set(ir);
342         this->clipRect(r, SkMatrix::I(), op, false);
343     }
344     void clipRect(const SkRect&, const SkMatrix& matrix, SkClipOp, bool doAA);
345     void clipRRect(const SkRRect&, const SkMatrix& matrix, SkClipOp, bool doAA);
346     void clipPath(const SkPath&, const SkMatrix& matrix, SkClipOp, bool doAA);
347     void clipShader(sk_sp<SkShader>);
348     // An optimized version of clipDevRect(emptyRect, kIntersect, ...)
349     void clipEmpty();
350 
351     void replaceClip(const SkRect& devRect, bool doAA);
352 
353     /**
354      * isWideOpen returns true if the clip state corresponds to the infinite
355      * plane (i.e., draws are not limited at all)
356      */
isWideOpen()357     bool isWideOpen() const { return this->getTopmostGenID() == kWideOpenGenID; }
358 
359     /**
360      * This method quickly and conservatively determines whether the entire stack is equivalent to
361      * intersection with a rrect given a bounds, where the rrect must not contain the entire bounds.
362      *
363      * @param bounds   A bounds on what will be drawn through the clip. The clip only need be
364      *                 equivalent to a intersection with a rrect for draws within the bounds. The
365      *                 returned rrect must intersect the bounds but need not be contained by the
366      *                 bounds.
367      * @param rrect    If return is true rrect will contain the rrect equivalent to the stack.
368      * @param aa       If return is true aa will indicate whether the equivalent rrect clip is
369      *                 antialiased.
370      * @return true if the stack is equivalent to a single rrect intersect clip, false otherwise.
371      */
372     bool isRRect(const SkRect& bounds, SkRRect* rrect, bool* aa) const;
373 
374     /**
375      * The generation ID has three reserved values to indicate special
376      * (potentially ignorable) cases
377      */
378     static const uint32_t kInvalidGenID  = 0;    //!< Invalid id that is never returned by
379                                                  //!< SkClipStack. Useful when caching clips
380                                                  //!< based on GenID.
381     static const uint32_t kEmptyGenID    = 1;    // no pixels writeable
382     static const uint32_t kWideOpenGenID = 2;    // all pixels writeable
383 
384     uint32_t getTopmostGenID() const;
385 
386 #ifdef SK_DEBUG
387     /**
388      * Dumps the contents of the clip stack to SkDebugf. This is intended for Skia development
389      * debugging. Don't rely on the existence of this function or the formatting of its output.
390      */
391     void dump() const;
392 #endif
393 
394 public:
395     class Iter {
396     public:
397         enum IterStart {
398             kBottom_IterStart = SkDeque::Iter::kFront_IterStart,
399             kTop_IterStart = SkDeque::Iter::kBack_IterStart
400         };
401 
402         /**
403          * Creates an uninitialized iterator. Must be reset()
404          */
405         Iter();
406 
407         Iter(const SkClipStack& stack, IterStart startLoc);
408 
409         /**
410          *  Return the clip element for this iterator. If next()/prev() returns NULL, then the
411          *  iterator is done.
412          */
413         const Element* next();
414         const Element* prev();
415 
416         /**
417          * Moves the iterator to the topmost element with the specified RegionOp and returns that
418          * element. If no clip element with that op is found, the first element is returned.
419          */
420         const Element* skipToTopmost(SkClipOp op);
421 
422         /**
423          * Restarts the iterator on a clip stack.
424          */
425         void reset(const SkClipStack& stack, IterStart startLoc);
426 
427     private:
428         const SkClipStack* fStack;
429         SkDeque::Iter      fIter;
430     };
431 
432     /**
433      * The B2TIter iterates from the bottom of the stack to the top.
434      * It inherits privately from Iter to prevent access to reverse iteration.
435      */
436     class B2TIter : private Iter {
437     public:
B2TIter()438         B2TIter() {}
439 
440         /**
441          * Wrap Iter's 2 parameter ctor to force initialization to the
442          * beginning of the deque/bottom of the stack
443          */
B2TIter(const SkClipStack & stack)444         B2TIter(const SkClipStack& stack)
445         : INHERITED(stack, kBottom_IterStart) {
446         }
447 
448         using Iter::next;
449 
450         /**
451          * Wrap Iter::reset to force initialization to the
452          * beginning of the deque/bottom of the stack
453          */
reset(const SkClipStack & stack)454         void reset(const SkClipStack& stack) {
455             this->INHERITED::reset(stack, kBottom_IterStart);
456         }
457 
458     private:
459 
460         using INHERITED = Iter;
461     };
462 
463     /**
464      * GetConservativeBounds returns a conservative bound of the current clip.
465      * Since this could be the infinite plane (if inverse fills were involved) the
466      * maxWidth and maxHeight parameters can be used to limit the returned bound
467      * to the expected drawing area. Similarly, the offsetX and offsetY parameters
468      * allow the caller to offset the returned bound to account for translated
469      * drawing areas (i.e., those resulting from a saveLayer). For finite bounds,
470      * the translation (+offsetX, +offsetY) is applied before the clamp to the
471      * maximum rectangle: [0,maxWidth) x [0,maxHeight).
472      * isIntersectionOfRects is an optional parameter that is true when
473      * 'devBounds' is the result of an intersection of rects. In this case
474      * 'devBounds' is the exact answer/clip.
475      */
476     void getConservativeBounds(int offsetX,
477                                int offsetY,
478                                int maxWidth,
479                                int maxHeight,
480                                SkRect* devBounds,
481                                bool* isIntersectionOfRects = nullptr) const;
482 
483 private:
484     friend class Iter;
485 
486     SkDeque fDeque;
487     int     fSaveCount;
488 
489     bool internalQuickContains(const SkRect& devRect) const;
490     bool internalQuickContains(const SkRRect& devRRect) const;
491 
492     /**
493      * Helper for clipDevPath, etc.
494      */
495     void pushElement(const Element& element);
496 
497     /**
498      * Restore the stack back to the specified save count.
499      */
500     void restoreTo(int saveCount);
501 
502     /**
503      * Return the next unique generation ID.
504      */
505     static uint32_t GetNextGenID();
506 };
507 
508 #endif
509