/* | |

* 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<int>(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<uint8_t>(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 |