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