Coverage Report

Created: 2025-06-24 08:20

/src/skia/src/core/SkClipStack.h
Line
Count
Source (jump to first uncovered line)
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
69
0
        Element() {
70
0
            this->initCommon(0, SkClipOp::kIntersect, false);
71
0
            this->setEmpty();
72
0
        }
73
74
        Element(const Element&);
75
76
0
        Element(const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
77
0
            this->initRect(0, rect, m, op, doAA);
78
0
        }
79
80
0
        Element(const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
81
0
            this->initRRect(0, rrect, m, op, doAA);
82
0
        }
83
84
0
        Element(const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
85
0
            this->initPath(0, path, m, op, doAA);
86
0
        }
87
88
0
        Element(sk_sp<SkShader> shader) {
89
0
            this->initShader(0, std::move(shader));
90
0
        }
91
92
0
        Element(const SkRect& rect, bool doAA) {
93
0
            this->initReplaceRect(0, rect, doAA);
94
0
        }
95
96
        ~Element();
97
98
        bool operator== (const Element& element) const;
99
0
        bool operator!= (const Element& element) const { return !(*this == element); }
100
101
        //!< Call to get the type of the clip element.
102
24.5k
        DeviceSpaceType getDeviceSpaceType() const { return fDeviceSpaceType; }
103
104
        //!< Call to get the save count associated with this clip element.
105
0
        int getSaveCount() const { return fSaveCount; }
106
107
        //!< Call if getDeviceSpaceType() is kPath to get the path.
108
6.62k
        const SkPath& getDeviceSpacePath() const {
109
6.62k
            SkASSERT(DeviceSpaceType::kPath == fDeviceSpaceType);
110
6.62k
            return *fDeviceSpacePath;
111
6.62k
        }
112
113
        //!< Call if getDeviceSpaceType() is kRRect to get the round-rect.
114
83
        const SkRRect& getDeviceSpaceRRect() const {
115
83
            SkASSERT(DeviceSpaceType::kRRect == fDeviceSpaceType);
116
83
            return fDeviceSpaceRRect;
117
83
        }
118
119
        //!< Call if getDeviceSpaceType() is kRect to get the rect.
120
19.4k
        const SkRect& getDeviceSpaceRect() const {
121
19.4k
            SkASSERT(DeviceSpaceType::kRect == fDeviceSpaceType &&
122
19.4k
                     (fDeviceSpaceRRect.isRect() || fDeviceSpaceRRect.isEmpty()));
123
19.4k
            return fDeviceSpaceRRect.getBounds();
124
19.4k
        }
125
126
        //!<Call if getDeviceSpaceType() is kShader to get a reference to the clip shader.
127
0
        sk_sp<SkShader> refShader() const {
128
0
            return fShader;
129
0
        }
130
0
        const SkShader* getShader() const {
131
0
            return fShader.get();
132
0
        }
133
134
        //!< Call if getDeviceSpaceType() is not kEmpty to get the set operation used to combine
135
        //!< this element.
136
6.44k
        SkClipOp getOp() const { return fOp; }
137
        // Augments getOps()'s behavior by requiring a clip reset before the op is applied.
138
43.1k
        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.
144
0
        const SkRRect& asDeviceSpaceRRect() const {
145
0
            SkASSERT(DeviceSpaceType::kPath != fDeviceSpaceType);
146
0
            return fDeviceSpaceRRect;
147
0
        }
Unexecuted instantiation: SkClipStack::Element::asDeviceSpaceRRect() const
Unexecuted instantiation: SkClipStack::Element::asDeviceSpaceRRect() const
148
149
        /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased
150
            when it is rasterized. */
151
1.08k
        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. */
161
10.1k
        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
         */
179
0
        bool isInverseFilled() const {
180
0
            return DeviceSpaceType::kPath == fDeviceSpaceType &&
181
0
                   fDeviceSpacePath->isInverseFillType();
182
0
        }
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;
222
0
        Element(int saveCount) {
223
0
            this->initCommon(saveCount, SkClipOp::kIntersect, false);
224
0
            this->setEmpty();
225
0
        }
226
227
1.21k
        Element(int saveCount, const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
228
1.21k
            this->initRRect(saveCount, rrect, m, op, doAA);
229
1.21k
        }
230
231
17.2k
        Element(int saveCount, const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
232
17.2k
            this->initRect(saveCount, rect, m, op, doAA);
233
17.2k
        }
234
235
5.72k
        Element(int saveCount, const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
236
5.72k
            this->initPath(saveCount, path, m, op, doAA);
237
5.72k
        }
238
239
0
        Element(int saveCount, sk_sp<SkShader> shader) {
240
0
            this->initShader(saveCount, std::move(shader));
241
0
        }
242
243
0
        Element(int saveCount, const SkRect& rect, bool doAA) {
244
0
            this->initReplaceRect(saveCount, rect, doAA);
245
0
        }
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
0
    bool operator!=(const SkClipStack& b) const { return !(*this == b); }
287
288
    void reset();
289
290
9.14k
    int getSaveCount() const { return fSaveCount; }
291
    void save();
292
    void restore();
293
294
    class AutoRestore {
295
    public:
296
        AutoRestore(SkClipStack* cs, bool doSave)
297
4.22k
            : fCS(cs), fSaveCount(cs->getSaveCount())
298
4.22k
        {
299
4.22k
            if (doSave) {
300
0
                fCS->save();
301
0
            }
302
4.22k
        }
303
4.22k
        ~AutoRestore() {
304
4.22k
            SkASSERT(fCS->getSaveCount() >= fSaveCount);  // no underflow
305
4.92k
            while (fCS->getSaveCount() > fSaveCount) {
306
697
                fCS->restore();
307
697
            }
308
4.22k
        }
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
     */
336
0
    bool quickContains(const SkRect& devRect) const {
337
0
        return this->isWideOpen() || this->internalQuickContains(devRect);
338
0
    }
339
340
0
    bool quickContains(const SkRRect& devRRect) const {
341
0
        return this->isWideOpen() || this->internalQuickContains(devRRect);
342
0
    }
343
344
0
    void clipDevRect(const SkIRect& ir, SkClipOp op) {
345
0
        SkRect r;
346
0
        r.set(ir);
347
0
        this->clipRect(r, SkMatrix::I(), op, false);
348
0
    }
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
     */
362
0
    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:
443
0
        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
         */
449
        B2TIter(const SkClipStack& stack)
450
14.2k
        : INHERITED(stack, kBottom_IterStart) {
451
14.2k
        }
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
         */
459
0
        void reset(const SkClipStack& stack) {
460
0
            this->INHERITED::reset(stack, kBottom_IterStart);
461
0
        }
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