/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 |