GrShape.h revision 7049396b65660907af5292d899053280430d929a
1/* 2 * Copyright 2016 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 GrShape_DEFINED 9#define GrShape_DEFINED 10 11#include "GrStyle.h" 12#include "SkPath.h" 13#include "SkPathPriv.h" 14#include "SkRRect.h" 15#include "SkTemplates.h" 16#include "SkTLazy.h" 17 18/** 19 * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with. 20 * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry 21 * reflects the styling information (e.g. is stroked). It is also possible to apply just the 22 * path effect from the style. In this case the resulting shape will include any remaining 23 * stroking information that is to be applied after the path effect. 24 * 25 * Shapes can produce keys that represent only the geometry information, not the style. Note that 26 * when styling information is applied to produce a new shape then the style has been converted 27 * to geometric information and is included in the new shape's key. When the same style is applied 28 * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes 29 * will be the same. 30 * 31 * Currently this can only be constructed from a path, rect, or rrect though it can become a path 32 * applying style to the geometry. The idea is to expand this to cover most or all of the geometries 33 * that have SkCanvas::draw APIs. 34 */ 35class GrShape { 36public: 37 GrShape() : fType(Type::kEmpty) {} 38 39 explicit GrShape(const SkPath& path) 40 : fType(Type::kPath) 41 , fPath(&path) { 42 this->attemptToReduceFromPath(); 43 } 44 45 explicit GrShape(const SkRRect& rrect) 46 : fType(Type::kRRect) 47 , fRRect(rrect) 48 , fRRectIsInverted(false) { 49 fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir); 50 this->attemptToReduceFromRRect(); 51 } 52 53 explicit GrShape(const SkRect& rect) 54 : fType(Type::kRRect) 55 , fRRect(SkRRect::MakeRect(rect)) 56 , fRRectIsInverted(false) { 57 fRRectStart = DefaultRectDirAndStartIndex(rect, false, &fRRectDir); 58 this->attemptToReduceFromRRect(); 59 } 60 61 GrShape(const SkPath& path, const GrStyle& style) 62 : fType(Type::kPath) 63 , fPath(&path) 64 , fStyle(style) { 65 this->attemptToReduceFromPath(); 66 } 67 68 GrShape(const SkRRect& rrect, const GrStyle& style) 69 : fType(Type::kRRect) 70 , fRRect(rrect) 71 , fRRectIsInverted(false) 72 , fStyle(style) { 73 fRRectStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(), &fRRectDir); 74 this->attemptToReduceFromRRect(); 75 } 76 77 GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted, 78 const GrStyle& style) 79 : fType(Type::kRRect) 80 , fRRect(rrect) 81 , fRRectIsInverted(inverted) 82 , fStyle(style) { 83 if (style.pathEffect()) { 84 fRRectDir = dir; 85 fRRectStart = start; 86 if (fRRect.getType() == SkRRect::kRect_Type) { 87 fRRectStart = (fRRectStart + 1) & 0b110; 88 } else if (fRRect.getType() == SkRRect::kOval_Type) { 89 fRRectStart &= 0b110; 90 } 91 } else { 92 fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir); 93 } 94 this->attemptToReduceFromRRect(); 95 } 96 97 GrShape(const SkRect& rect, const GrStyle& style) 98 : fType(Type::kRRect) 99 , fRRect(SkRRect::MakeRect(rect)) 100 , fRRectIsInverted(false) 101 , fStyle(style) { 102 fRRectStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(), &fRRectDir); 103 this->attemptToReduceFromRRect(); 104 } 105 106 GrShape(const SkPath& path, const SkPaint& paint) 107 : fType(Type::kPath) 108 , fPath(&path) 109 , fStyle(paint) { 110 this->attemptToReduceFromPath(); 111 } 112 113 GrShape(const SkRRect& rrect, const SkPaint& paint) 114 : fType(Type::kRRect) 115 , fRRect(rrect) 116 , fRRectIsInverted(false) 117 , fStyle(paint) { 118 fRRectStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(), &fRRectDir); 119 this->attemptToReduceFromRRect(); 120 } 121 122 GrShape(const SkRect& rect, const SkPaint& paint) 123 : fType(Type::kRRect) 124 , fRRect(SkRRect::MakeRect(rect)) 125 , fRRectIsInverted(false) 126 , fStyle(paint) { 127 fRRectStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(), &fRRectDir); 128 this->attemptToReduceFromRRect(); 129 } 130 131 GrShape(const GrShape&); 132 GrShape& operator=(const GrShape& that); 133 134 ~GrShape() { 135 if (Type::kPath == fType) { 136 fPath.reset(); 137 } 138 } 139 140 const GrStyle& style() const { return fStyle; } 141 142 /** 143 * Returns a shape that has either applied the path effect or path effect and stroking 144 * information from this shape's style to its geometry. Scale is used when approximating the 145 * output geometry and typically is computed from the view matrix 146 */ 147 GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) { 148 return GrShape(*this, apply, scale); 149 } 150 151 /** Returns the unstyled geometry as a rrect if possible. */ 152 bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const { 153 if (Type::kRRect != fType) { 154 return false; 155 } 156 if (rrect) { 157 *rrect = fRRect; 158 } 159 if (dir) { 160 *dir = fRRectDir; 161 } 162 if (start) { 163 *start = fRRectStart; 164 } 165 if (inverted) { 166 *inverted = fRRectIsInverted; 167 } 168 return true; 169 } 170 171 /** Returns the unstyled geometry as a path. */ 172 void asPath(SkPath* out) const { 173 switch (fType) { 174 case Type::kEmpty: 175 out->reset(); 176 break; 177 case Type::kRRect: 178 out->reset(); 179 out->addRRect(fRRect, fRRectDir, fRRectStart); 180 if (fRRectIsInverted) { 181 out->setFillType(SkPath::kInverseWinding_FillType); 182 } 183 break; 184 case Type::kPath: 185 *out = *fPath.get(); 186 break; 187 } 188 } 189 190 /** 191 * Returns whether the geometry is empty. Note that applying the style could produce a 192 * non-empty shape. 193 */ 194 bool isEmpty() const { return Type::kEmpty == fType; } 195 196 /** 197 * Gets the bounds of the geometry without reflecting the shape's styling. This ignores 198 * the inverse fill nature of the geometry. 199 */ 200 const SkRect& bounds() const; 201 202 /** 203 * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill 204 * status). 205 */ 206 void styledBounds(SkRect* bounds) const; 207 208 /** 209 * Is it known that the unstyled geometry has no unclosed contours. This means that it will 210 * not have any caps if stroked (modulo the effect of any path effect). 211 */ 212 bool knownToBeClosed() const { 213 switch (fType) { 214 case Type::kEmpty: 215 return true; 216 case Type::kRRect: 217 return true; 218 case Type::kPath: 219 return false; 220 } 221 return false; 222 } 223 224 uint32_t segmentMask() const { 225 switch (fType) { 226 case Type::kEmpty: 227 return 0; 228 case Type::kRRect: 229 if (fRRect.getType() == SkRRect::kOval_Type) { 230 return SkPath::kConic_SegmentMask; 231 } else if (fRRect.getType() == SkRRect::kRect_Type) { 232 return SkPath::kLine_SegmentMask; 233 } 234 return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask; 235 case Type::kPath: 236 return fPath.get()->getSegmentMasks(); 237 } 238 return 0; 239 } 240 241 /** 242 * Gets the size of the key for the shape represented by this GrShape (ignoring its styling). 243 * A negative value is returned if the shape has no key (shouldn't be cached). 244 */ 245 int unstyledKeySize() const; 246 247 /** 248 * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough 249 * space allocated for the key and that unstyledKeySize() does not return a negative value 250 * for this shape. 251 */ 252 void writeUnstyledKey(uint32_t* key) const; 253 254private: 255 enum class Type { 256 kEmpty, 257 kRRect, 258 kPath, 259 }; 260 261 /** Constructor used by the applyStyle() function */ 262 GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale); 263 264 /** 265 * Determines the key we should inherit from the input shape's geometry and style when 266 * we are applying the style to create a new shape. 267 */ 268 void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale); 269 270 void attemptToReduceFromPath() { 271 SkASSERT(Type::kPath == fType); 272 fType = AttemptToReduceFromPathImpl(*fPath.get(), &fRRect, &fRRectDir, &fRRectStart, 273 &fRRectIsInverted, fStyle.pathEffect(), 274 fStyle.strokeRec()); 275 if (Type::kPath != fType) { 276 fPath.reset(); 277 fInheritedKey.reset(0); 278 } 279 } 280 281 void attemptToReduceFromRRect() { 282 SkASSERT(Type::kRRect == fType); 283 SkASSERT(!fInheritedKey.count()); 284 if (fRRectIsInverted) { 285 if (!fStyle.hasNonDashPathEffect()) { 286 SkStrokeRec::Style recStyle = fStyle.strokeRec().getStyle(); 287 if (SkStrokeRec::kStroke_Style == recStyle || 288 SkStrokeRec::kHairline_Style == recStyle) { 289 // stroking ignores the path fill rule. 290 fRRectIsInverted = false; 291 } 292 } 293 } else if (fRRect.isEmpty()) { 294 fType = Type::kEmpty; 295 } 296 } 297 298 static Type AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect, 299 SkPath::Direction* rrectDir, unsigned* rrectStart, 300 bool* rrectIsInverted, const SkPathEffect* pe, 301 const SkStrokeRec& strokeRec); 302 303 static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction; 304 static constexpr unsigned kDefaultRRectStart = 0; 305 306 static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect, 307 SkPath::Direction* dir) { 308 *dir = kDefaultRRectDir; 309 // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise 310 // beginning at index 0 (which happens to correspond to rrect index 0 or 7). 311 if (!hasPathEffect) { 312 // It doesn't matter what start we use, just be consistent to avoid redundant keys. 313 return kDefaultRRectStart; 314 } 315 // In SkPath a rect starts at index 0 by default. This is the top left corner. However, 316 // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the 317 // rect edges. Thus, we may need to modify the rrect's start index to account for the sort. 318 bool swapX = rect.fLeft > rect.fRight; 319 bool swapY = rect.fTop > rect.fBottom; 320 if (swapX && swapY) { 321 // 0 becomes start index 2 and times 2 to convert from rect the rrect indices. 322 return 2 * 2; 323 } else if (swapX) { 324 *dir = SkPath::kCCW_Direction; 325 // 0 becomes start index 1 and times 2 to convert from rect the rrect indices. 326 return 2 * 1; 327 } else if (swapY) { 328 *dir = SkPath::kCCW_Direction; 329 // 0 becomes start index 3 and times 2 to convert from rect the rrect indices. 330 return 2 * 3; 331 } 332 return 0; 333 } 334 335 static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect, 336 SkPath::Direction* dir) { 337 // This comes from SkPath's interface. The default for adding a SkRRect to a path is 338 // clockwise beginning at starting index 6. 339 static constexpr unsigned kPathRRectStartIdx = 6; 340 *dir = kDefaultRRectDir; 341 if (!hasPathEffect) { 342 // It doesn't matter what start we use, just be consistent to avoid redundant keys. 343 return kDefaultRRectStart; 344 } 345 return kPathRRectStartIdx; 346 } 347 348 Type fType; 349 SkRRect fRRect; 350 SkPath::Direction fRRectDir; 351 unsigned fRRectStart; 352 bool fRRectIsInverted; 353 SkTLazy<SkPath> fPath; 354 GrStyle fStyle; 355 SkAutoSTArray<8, uint32_t> fInheritedKey; 356}; 357#endif 358