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