/* * Copyright 2020 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrShape_DEFINED #define GrShape_DEFINED #include "include/core/SkPath.h" #include "include/core/SkPoint.h" #include "include/core/SkRRect.h" #include "include/core/SkRect.h" // Represents an arc along an oval boundary, or a closed wedge of the oval. struct GrArc { SkRect fOval; // The sorted, bounding box defining the oval the arc is traced along SkScalar fStartAngle; // In degrees SkScalar fSweepAngle; // In degrees bool fUseCenter; // True if the arc includes the center point of the oval }; // Represents a line segment between two points. struct GrLineSegment { SkPoint fP1; SkPoint fP2; }; /** * GrShape is a convenience class to represent the many different specialized geometries that * Ganesh can handle, including rects, round rects, lines, as well as paths. It is intended as * a data-only class where any additional complex behavior is handled by an owning type (e.g. * GrStyledShape). However, it does include some basic utilities that unify common functionality * (such as contains()) from the underlying shape types. * * In order to have lossless simplification of the geometry, it also tracks winding direction, start * index, and fill inversion. The direction and index are match the SkPath indexing scheme for * the shape's type (e.g. rect, rrect, or oval). * * Regarding GrShape's empty shape: * - GrShape uses empty to refer to the absence of any geometric data * - SkRect::isEmpty() returns true if the rect is not sorted, even if it has area. GrShape will not * simplify these shapes to an empty GrShape. Rects with actual 0 width and height will simplify * to a point or line, not empty. This is to preserve geometric data for path effects and strokes. * - SkRRect::isEmpty() is true when the bounds have 0 width or height, so GrShape will simplify it * to a point or line, just like a rect. SkRRect does not have the concept of unsorted edges. */ class GrShape { public: // The current set of types GrShape can represent directly enum class Type : uint8_t { kEmpty, kPoint, kRect, kRRect, kPath, kArc, kLine }; static constexpr int kTypeCount = static_cast(Type::kLine) + 1; // The direction and start index used when a shape does not have a representable winding, // or when that information was discarded during simplification (kIgnoreWinding_Flag). static constexpr SkPathDirection kDefaultDir = SkPathDirection::kCW; static constexpr unsigned kDefaultStart = 0; // The fill rule that is used by asPath() for shapes that aren't already a path. static constexpr SkPathFillType kDefaultFillType = SkPathFillType::kEvenOdd; GrShape() {} explicit GrShape(const SkPoint& point) { this->setPoint(point); } explicit GrShape(const SkRect& rect) { this->setRect(rect); } explicit GrShape(const SkRRect& rrect) { this->setRRect(rrect); } explicit GrShape(const SkPath& path) { this->setPath(path); } explicit GrShape(const GrArc& arc) { this->setArc(arc); } explicit GrShape(const GrLineSegment& line){ this->setLine(line); } GrShape(const GrShape& shape) { *this = shape; } ~GrShape() { this->reset(); } // NOTE: None of the geometry types benefit from move semantics, so we don't bother // defining a move assignment operator for GrShape. GrShape& operator=(const GrShape& shape); // These type queries reflect the shape type provided when assigned, it does not incorporate // any potential simplification (e.g. if isRRect() is true and rrect().isRect() is true, // isRect() will still be false, until simplify() is called). bool isEmpty() const { return this->type() == Type::kEmpty; } bool isPoint() const { return this->type() == Type::kPoint; } bool isRect() const { return this->type() == Type::kRect; } bool isRRect() const { return this->type() == Type::kRRect; } bool isPath() const { return this->type() == Type::kPath; } bool isArc() const { return this->type() == Type::kArc; } bool isLine() const { return this->type() == Type::kLine; } Type type() const { return fType; } // Report the shape type, winding direction, start index, and invertedness as a value suitable // for use in a resource key. This does not include any geometry coordinates into the key value. uint32_t stateKey() const; // Whether or not the shape is meant to be the inverse of its geometry (i.e. its exterior). bool inverted() const { return this->isPath() ? fPath.isInverseFillType() : SkToBool(fInverted); } // Returns the path direction extracted from the path during simplification, if the shape's // type represents a rrect, rect, or oval. SkPathDirection dir() const { return fCW ? SkPathDirection::kCW : SkPathDirection::kCCW; } // Returns the start index extracted from the path during simplification, if the shape's // type represents a rrect, rect, or oval. unsigned startIndex() const { return fStart; } // Override the direction and start parameters for the simplified contour. These are only // meaningful for rects, rrects, and ovals. void setPathWindingParams(SkPathDirection dir, unsigned start) { SkASSERT((this->isRect() && start < 4) || (this->isRRect() && start < 8) || (dir == kDefaultDir && start == kDefaultStart)); fCW = dir == SkPathDirection::kCW; fStart = static_cast(start); } void setInverted(bool inverted) { if (this->isPath()) { if (inverted != fPath.isInverseFillType()) { fPath.toggleInverseFillType(); } } else { fInverted = inverted; } } // Access the actual geometric description of the shape. May only access the appropriate type // based on what was last set. The type may change after simplify() is called. SkPoint& point() { SkASSERT(this->isPoint()); return fPoint; } const SkPoint& point() const { SkASSERT(this->isPoint()); return fPoint; } SkRect& rect() { SkASSERT(this->isRect()); return fRect; } const SkRect& rect() const { SkASSERT(this->isRect()); return fRect; } SkRRect& rrect() { SkASSERT(this->isRRect()); return fRRect; } const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; } SkPath& path() { SkASSERT(this->isPath()); return fPath; } const SkPath& path() const { SkASSERT(this->isPath()); return fPath; } GrArc& arc() { SkASSERT(this->isArc()); return fArc; } const GrArc& arc() const { SkASSERT(this->isArc()); return fArc; } GrLineSegment& line() { SkASSERT(this->isLine()); return fLine; } const GrLineSegment& line() const { SkASSERT(this->isLine()); return fLine; } // Update the geometry stored in the GrShape and update its associated type to match. This // performs no simplification, so calling setRRect() with a round rect that has isRect() return // true will still be considered an rrect by this shape until simplify() is called. // // These also reset any extracted direction, start, and inverted state from a prior simplified // path, since these functions ared used to describe a new geometry. void setPoint(const SkPoint& point) { this->reset(Type::kPoint); fPoint = point; } void setRect(const SkRect& rect) { this->reset(Type::kRect); fRect = rect; } void setRRect(const SkRRect& rrect) { this->reset(Type::kRRect); fRRect = rrect; } void setArc(const GrArc& arc) { this->reset(Type::kArc); fArc = arc; } void setLine(const GrLineSegment& line) { this->reset(Type::kLine); fLine = line; } void setPath(const SkPath& path) { if (this->isPath()) { // Assign directly fPath = path; } else { // In-place initialize this->setType(Type::kPath); new (&fPath) SkPath(path); } // Must also set these since we didn't call reset() like other setX functions. this->setPathWindingParams(kDefaultDir, kDefaultStart); fInverted = path.isInverseFillType(); } void reset() { this->reset(Type::kEmpty); } // Flags that enable more aggressive, "destructive" simplifications to the geometry enum SimplifyFlags : unsigned { // If set, it is assumed the original shape would have been implicitly filled when drawn or // clipped, so simpler shape types that are closed can still be considered. Shapes with // 0 area (i.e. points and lines) can be turned into empty. kSimpleFill_Flag = 0b001, // If set, simplifications that would impact a directional stroke or path effect can still // be taken (e.g. dir and start are not required, arcs can be converted to ovals). kIgnoreWinding_Flag = 0b010, // If set, the geometry will be updated to have sorted coordinates (rects, lines), modulated // sweep angles (arcs). kMakeCanonical_Flag = 0b100, kAll_Flags = 0b111 }; // Returns true if the shape was originally closed based on type (or detected type within a // path), even if the final simplification results in a point, line, or empty. bool simplify(unsigned flags = kAll_Flags); // True if the given bounding box is completely inside the shape, if it's conservatively treated // as a filled, closed shape. bool conservativeContains(const SkRect& rect) const; bool conservativeContains(const SkPoint& point) const; // True if the underlying geometry represents a closed shape, without the need for an // implicit close (note that if simplified earlier with 'simpleFill' = true, a shape that was // not closed may become closed). bool closed() const; // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill' // is true, it is assumed the contours will be implicitly closed when drawn or used. bool convex(bool simpleFill = true) const; // The bounding box of the shape. SkRect bounds() const; // The segment masks that describe the shape, were it to be converted to an SkPath uint32_t segmentMask() const; // Convert the shape into a path that describes the same geometry. void asPath(SkPath* out, bool simpleFill = true) const; private: void setType(Type type) { if (this->isPath() && type != Type::kPath) { fInverted = fPath.isInverseFillType(); fPath.~SkPath(); } fType = type; } void reset(Type type) { this->setType(type); this->setPathWindingParams(kDefaultDir, kDefaultStart); this->setInverted(false); } // Paths and arcs are root shapes, another type will never simplify to them, so they do // not take the geometry to simplify as an argument. Since they are root shapes, they also // return whether or not they were originally closed before being simplified. bool simplifyPath(unsigned flags); bool simplifyArc(unsigned flags); // The simpler type classes do take the geometry because it may represent an in-progress // simplification that hasn't been set on the GrShape yet. The simpler types do not report // whether or not they were closed because it's implicit in their type. void simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags); void simplifyPoint(const SkPoint& point, unsigned flags); // RRects and rects care about winding for path effects and will set the path winding state // of the shape as well. void simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags); void simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags); union { SkPoint fPoint; SkRect fRect; SkRRect fRRect; SkPath fPath; GrArc fArc; GrLineSegment fLine; }; Type fType = Type::kEmpty; uint8_t fStart; // Restricted to rrects and simpler, so this will be < 8 bool fCW; bool fInverted; }; #endif