| 1 | /* |
| 2 | * Copyright 2020 Google LLC |
| 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 "include/core/SkPath.h" |
| 12 | #include "include/core/SkPoint.h" |
| 13 | #include "include/core/SkRRect.h" |
| 14 | #include "include/core/SkRect.h" |
| 15 | |
| 16 | // Represents an arc along an oval boundary, or a closed wedge of the oval. |
| 17 | struct GrArc { |
| 18 | SkRect fOval; // The sorted, bounding box defining the oval the arc is traced along |
| 19 | SkScalar fStartAngle; // In degrees |
| 20 | SkScalar fSweepAngle; // In degrees |
| 21 | bool fUseCenter; // True if the arc includes the center point of the oval |
| 22 | }; |
| 23 | |
| 24 | // Represents a line segment between two points. |
| 25 | struct GrLineSegment { |
| 26 | SkPoint fP1; |
| 27 | SkPoint fP2; |
| 28 | }; |
| 29 | |
| 30 | /** |
| 31 | * GrShape is a convenience class to represent the many different specialized geometries that |
| 32 | * Ganesh can handle, including rects, round rects, lines, as well as paths. It is intended as |
| 33 | * a data-only class where any additional complex behavior is handled by an owning type (e.g. |
| 34 | * GrStyledShape). However, it does include some basic utilities that unify common functionality |
| 35 | * (such as contains()) from the underlying shape types. |
| 36 | * |
| 37 | * In order to have lossless simplification of the geometry, it also tracks winding direction, start |
| 38 | * index, and fill inversion. The direction and index are match the SkPath indexing scheme for |
| 39 | * the shape's type (e.g. rect, rrect, or oval). |
| 40 | * |
| 41 | * Regarding GrShape's empty shape: |
| 42 | * - GrShape uses empty to refer to the absence of any geometric data |
| 43 | * - SkRect::isEmpty() returns true if the rect is not sorted, even if it has area. GrShape will not |
| 44 | * simplify these shapes to an empty GrShape. Rects with actual 0 width and height will simplify |
| 45 | * to a point or line, not empty. This is to preserve geometric data for path effects and strokes. |
| 46 | * - SkRRect::isEmpty() is true when the bounds have 0 width or height, so GrShape will simplify it |
| 47 | * to a point or line, just like a rect. SkRRect does not have the concept of unsorted edges. |
| 48 | */ |
| 49 | class GrShape { |
| 50 | public: |
| 51 | // The current set of types GrShape can represent directly |
| 52 | enum class Type : uint8_t { |
| 53 | kEmpty, kPoint, kRect, kRRect, kPath, kArc, kLine, kLast = kLine |
| 54 | }; |
| 55 | static constexpr int kTypeCount = static_cast<int>(Type::kLast) + 1; |
| 56 | |
| 57 | // The direction and start index used when a shape does not have a representable winding, |
| 58 | // or when that information was discarded during simplification (kIgnoreWinding_Flag). |
| 59 | static constexpr SkPathDirection kDefaultDir = SkPathDirection::kCW; |
| 60 | static constexpr unsigned kDefaultStart = 0; |
| 61 | // The fill rule that is used by asPath() for shapes that aren't already a path. |
| 62 | static constexpr SkPathFillType kDefaultFillType = SkPathFillType::kEvenOdd; |
| 63 | |
| 64 | GrShape() {} |
| 65 | explicit GrShape(const SkPoint& point) { this->setPoint(point); } |
| 66 | explicit GrShape(const SkRect& rect) { this->setRect(rect); } |
| 67 | explicit GrShape(const SkRRect& rrect) { this->setRRect(rrect); } |
| 68 | explicit GrShape(const SkPath& path) { this->setPath(path); } |
| 69 | explicit GrShape(const GrArc& arc) { this->setArc(arc); } |
| 70 | explicit GrShape(const GrLineSegment& line){ this->setLine(line); } |
| 71 | |
| 72 | explicit GrShape(const GrShape& shape) { *this = shape; } |
| 73 | |
| 74 | ~GrShape() { this->reset(); } |
| 75 | |
| 76 | // NOTE: None of the geometry types benefit from move semantics, so we don't bother |
| 77 | // defining a move assignment operator for GrShape. |
| 78 | GrShape& operator=(const GrShape& shape); |
| 79 | |
| 80 | // These type queries reflect the shape type provided when assigned, it does not incorporate |
| 81 | // any potential simplification (e.g. if isRRect() is true and rrect().isRect() is true, |
| 82 | // isRect() will still be false, until simplify() is called). |
| 83 | bool isEmpty() const { return this->type() == Type::kEmpty; } |
| 84 | bool isPoint() const { return this->type() == Type::kPoint; } |
| 85 | bool isRect() const { return this->type() == Type::kRect; } |
| 86 | bool isRRect() const { return this->type() == Type::kRRect; } |
| 87 | bool isPath() const { return this->type() == Type::kPath; } |
| 88 | bool isArc() const { return this->type() == Type::kArc; } |
| 89 | bool isLine() const { return this->type() == Type::kLine; } |
| 90 | |
| 91 | Type type() const { return fType; } |
| 92 | |
| 93 | // Report the shape type, winding direction, start index, and invertedness as a value suitable |
| 94 | // for use in a resource key. This does not include any geometry coordinates into the key value. |
| 95 | uint32_t stateKey() const; |
| 96 | |
| 97 | // Whether or not the shape is meant to be the inverse of its geometry (i.e. its exterior). |
| 98 | bool inverted() const { |
| 99 | return this->isPath() ? fPath.isInverseFillType() : SkToBool(fInverted); |
| 100 | } |
| 101 | |
| 102 | // Returns the path direction extracted from the path during simplification, if the shape's |
| 103 | // type represents a rrect, rect, or oval. |
| 104 | SkPathDirection dir() const { return fCW ? SkPathDirection::kCW : SkPathDirection::kCCW; } |
| 105 | // Returns the start index extracted from the path during simplification, if the shape's |
| 106 | // type represents a rrect, rect, or oval. |
| 107 | unsigned startIndex() const { return fStart; } |
| 108 | |
| 109 | // Override the direction and start parameters for the simplified contour. These are only |
| 110 | // meaningful for rects, rrects, and ovals. |
| 111 | void setPathWindingParams(SkPathDirection dir, unsigned start) { |
| 112 | SkASSERT((this->isRect() && start < 4) || (this->isRRect() && start < 8) || |
| 113 | (dir == kDefaultDir && start == kDefaultStart)); |
| 114 | fCW = dir == SkPathDirection::kCW; |
| 115 | fStart = static_cast<uint8_t>(start); |
| 116 | } |
| 117 | |
| 118 | void setInverted(bool inverted) { |
| 119 | if (this->isPath()) { |
| 120 | if (inverted != fPath.isInverseFillType()) { |
| 121 | fPath.toggleInverseFillType(); |
| 122 | } |
| 123 | } else { |
| 124 | fInverted = inverted; |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | // Access the actual geometric description of the shape. May only access the appropriate type |
| 129 | // based on what was last set. The type may change after simplify() is called. |
| 130 | SkPoint& point() { SkASSERT(this->isPoint()); return fPoint; } |
| 131 | const SkPoint& point() const { SkASSERT(this->isPoint()); return fPoint; } |
| 132 | |
| 133 | SkRect& rect() { SkASSERT(this->isRect()); return fRect; } |
| 134 | const SkRect& rect() const { SkASSERT(this->isRect()); return fRect; } |
| 135 | |
| 136 | SkRRect& rrect() { SkASSERT(this->isRRect()); return fRRect; } |
| 137 | const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; } |
| 138 | |
| 139 | SkPath& path() { SkASSERT(this->isPath()); return fPath; } |
| 140 | const SkPath& path() const { SkASSERT(this->isPath()); return fPath; } |
| 141 | |
| 142 | GrArc& arc() { SkASSERT(this->isArc()); return fArc; } |
| 143 | const GrArc& arc() const { SkASSERT(this->isArc()); return fArc; } |
| 144 | |
| 145 | GrLineSegment& line() { SkASSERT(this->isLine()); return fLine; } |
| 146 | const GrLineSegment& line() const { SkASSERT(this->isLine()); return fLine; } |
| 147 | |
| 148 | // Update the geometry stored in the GrShape and update its associated type to match. This |
| 149 | // performs no simplification, so calling setRRect() with a round rect that has isRect() return |
| 150 | // true will still be considered an rrect by this shape until simplify() is called. |
| 151 | // |
| 152 | // These also reset any extracted direction, start, and inverted state from a prior simplified |
| 153 | // path, since these functions ared used to describe a new geometry. |
| 154 | void setPoint(const SkPoint& point) { |
| 155 | this->reset(Type::kPoint); |
| 156 | fPoint = point; |
| 157 | } |
| 158 | void setRect(const SkRect& rect) { |
| 159 | this->reset(Type::kRect); |
| 160 | fRect = rect; |
| 161 | } |
| 162 | void setRRect(const SkRRect& rrect) { |
| 163 | this->reset(Type::kRRect); |
| 164 | fRRect = rrect; |
| 165 | } |
| 166 | void setArc(const GrArc& arc) { |
| 167 | this->reset(Type::kArc); |
| 168 | fArc = arc; |
| 169 | } |
| 170 | void setLine(const GrLineSegment& line) { |
| 171 | this->reset(Type::kLine); |
| 172 | fLine = line; |
| 173 | } |
| 174 | void setPath(const SkPath& path) { |
| 175 | if (this->isPath()) { |
| 176 | // Assign directly |
| 177 | fPath = path; |
| 178 | } else { |
| 179 | // In-place initialize |
| 180 | this->setType(Type::kPath); |
| 181 | new (&fPath) SkPath(path); |
| 182 | } |
| 183 | // Must also set these since we didn't call reset() like other setX functions. |
| 184 | this->setPathWindingParams(kDefaultDir, kDefaultStart); |
| 185 | fInverted = path.isInverseFillType(); |
| 186 | } |
| 187 | void reset() { |
| 188 | this->reset(Type::kEmpty); |
| 189 | } |
| 190 | |
| 191 | // Flags that enable more aggressive, "destructive" simplifications to the geometry |
| 192 | enum SimplifyFlags : unsigned { |
| 193 | // If set, it is assumed the original shape would have been implicitly filled when drawn or |
| 194 | // clipped, so simpler shape types that are closed can still be considered. Shapes with |
| 195 | // 0 area (i.e. points and lines) can be turned into empty. |
| 196 | kSimpleFill_Flag = 0b001, |
| 197 | // If set, simplifications that would impact a directional stroke or path effect can still |
| 198 | // be taken (e.g. dir and start are not required, arcs can be converted to ovals). |
| 199 | kIgnoreWinding_Flag = 0b010, |
| 200 | // If set, the geometry will be updated to have sorted coordinates (rects, lines), modulated |
| 201 | // sweep angles (arcs). |
| 202 | kMakeCanonical_Flag = 0b100, |
| 203 | |
| 204 | kAll_Flags = 0b111 |
| 205 | }; |
| 206 | // Returns true if the shape was originally closed based on type (or detected type within a |
| 207 | // path), even if the final simplification results in a point, line, or empty. |
| 208 | bool simplify(unsigned flags = kAll_Flags); |
| 209 | |
| 210 | // True if the given bounding box is completely inside the shape. |
| 211 | bool contains(const SkRect& rect) const; |
| 212 | |
| 213 | // True if the underlying geometry represents a closed shape, without the need for an |
| 214 | // implicit close (note that if simplified earlier with 'simpleFill' = true, a shape that was |
| 215 | // not closed may become closed). |
| 216 | bool closed() const; |
| 217 | |
| 218 | // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill' |
| 219 | // is true, it is assumed the contours will be implicitly closed when drawn or used. |
| 220 | bool convex(bool simpleFill = true) const; |
| 221 | |
| 222 | // The bounding box of the shape. |
| 223 | SkRect bounds() const; |
| 224 | |
| 225 | // The segment masks that describe the shape, were it to be converted to an SkPath |
| 226 | uint32_t segmentMask() const; |
| 227 | |
| 228 | // Convert the shape into a path that describes the same geometry. |
| 229 | void asPath(SkPath* out, bool simpleFill = true) const; |
| 230 | |
| 231 | private: |
| 232 | |
| 233 | void setType(Type type) { |
| 234 | if (this->isPath() && type != Type::kPath) { |
| 235 | fInverted = fPath.isInverseFillType(); |
| 236 | fPath.~SkPath(); |
| 237 | } |
| 238 | fType = type; |
| 239 | } |
| 240 | |
| 241 | void reset(Type type) { |
| 242 | this->setType(type); |
| 243 | this->setPathWindingParams(kDefaultDir, kDefaultStart); |
| 244 | this->setInverted(false); |
| 245 | } |
| 246 | |
| 247 | // Paths and arcs are root shapes, another type will never simplify to them, so they do |
| 248 | // not take the geometry to simplify as an argument. Since they are root shapes, they also |
| 249 | // return whether or not they were originally closed before being simplified. |
| 250 | bool simplifyPath(unsigned flags); |
| 251 | bool simplifyArc(unsigned flags); |
| 252 | |
| 253 | // The simpler type classes do take the geometry because it may represent an in-progress |
| 254 | // simplification that hasn't been set on the GrShape yet. The simpler types do not report |
| 255 | // whether or not they were closed because it's implicit in their type. |
| 256 | void simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags); |
| 257 | void simplifyPoint(const SkPoint& point, unsigned flags); |
| 258 | |
| 259 | // RRects and rects care about winding for path effects and will set the path winding state |
| 260 | // of the shape as well. |
| 261 | void simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags); |
| 262 | void simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags); |
| 263 | |
| 264 | union { |
| 265 | SkPoint fPoint; |
| 266 | SkRect fRect; |
| 267 | SkRRect fRRect; |
| 268 | SkPath fPath; |
| 269 | GrArc fArc; |
| 270 | GrLineSegment fLine; |
| 271 | }; |
| 272 | |
| 273 | Type fType = Type::kEmpty; |
| 274 | uint8_t fStart; // Restricted to rrects and simpler, so this will be < 8 |
| 275 | bool fCW; |
| 276 | bool fInverted; |
| 277 | }; |
| 278 | |
| 279 | #endif |
| 280 | |