/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef skgpu_geom_Shape_DEFINED #define skgpu_geom_Shape_DEFINED #include "include/core/SkM44.h" #include "include/core/SkPath.h" #include "include/core/SkRRect.h" #include "include/core/SkRect.h" #include "experimental/graphite/src/geom/Rect.h" #include namespace skgpu { /** * Shape is effectively a std::variant over different geometric shapes, with the most complex * being an SkPath. It provides a consistent way to query geometric properties, such as convexity, * point containment, or iteration. */ class Shape { public: enum class Type : uint8_t { kEmpty, kLine, kRect, kRRect, kPath }; inline static constexpr int kTypeCount = static_cast(Type::kPath) + 1; Shape() {} Shape(const Shape& shape) { *this = shape; } Shape(Shape&&) = delete; Shape(SkPoint p0, SkPoint p1) { this->setLine(p0, p1); } Shape(SkV2 p0, SkV2 p1) { this->setLine(p0, p1); } Shape(float2 p0, float2 p1) { this->setLine(p0, p1); } explicit Shape(const Rect& rect) { this->setRect(rect); } explicit Shape(const SkRect& rect) { this->setRect(rect); } explicit Shape(const SkRRect& rrect) { this->setRRect(rrect); } explicit Shape(const SkPath& path) { this->setPath(path); } ~Shape() { this->reset(); } // NOTE: None of the geometry types benefit from move semantics, so we don't bother // defining a move assignment operator for Shape. Shape& operator=(Shape&&) = delete; Shape& operator=(const Shape&); // Return the type of the data last stored in the Shape, which does not incorporate any possible // simplifications that could be applied to it (e.g. a degenerate round rect with 0 radius // corners is kRRect and not kRect). Type type() const { return fType; } bool isEmpty() const { return fType == Type::kEmpty; } bool isLine() const { return fType == Type::kLine; } bool isRect() const { return fType == Type::kRect; } bool isRRect() const { return fType == Type::kRRect; } bool isPath() const { return fType == Type::kPath; } bool inverted() const { SkASSERT(fType != Type::kPath || fInverted == fPath.isInverseFillType()); return fInverted; } void setInverted(bool inverted) { if (fType == Type::kPath && inverted != fPath.isInverseFillType()) { fPath.toggleInverseFillType(); } fInverted = inverted; } SkPathFillType fillType() const { if (fType == Type::kPath) { return fPath.getFillType(); // already incorporates invertedness } else { return fInverted ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd; } } // True if the given bounding box is completely inside the shape, if it's conservatively treated // as a filled, closed shape. bool conservativeContains(const Rect& rect) const; bool conservativeContains(float2 point) const; // True if the underlying geometry represents a closed shape, without the need for an // implicit close. 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. Rect bounds() const; // Convert the shape into a path that describes the same geometry. SkPath asPath() const; // Access the actual geometric description of the shape. May only access the appropriate type // based on what was last set. float2 p0() const { SkASSERT(this->isLine()); return fRect.topLeft(); } float2 p1() const { SkASSERT(this->isLine()); return fRect.botRight(); } const Rect& rect() const { SkASSERT(this->isRect()); return fRect; } const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; } const SkPath& path() const { SkASSERT(this->isPath()); return fPath; } // Update the geometry stored in the Shape 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 Shape. // // These reset inversion to the default for the geometric type. void setLine(SkPoint p0, SkPoint p1) { this->setLine(float2{p0.fX, p0.fY}, float2{p1.fX, p1.fY}); } void setLine(SkV2 p0, SkV2 p1) { this->setLine(float2{p0.x, p0.y}, float2{p1.x, p1.y}); } void setLine(float2 p0, float2 p1) { this->setType(Type::kLine); fRect = Rect(p0, p1); fInverted = false; } void setRect(const SkRect& rect) { this->setRect(Rect(rect)); } void setRect(const Rect& rect) { this->setType(Type::kRect); fRect = rect; fInverted = false; } void setRRect(const SkRRect& rrect) { this->setType(Type::kRRect); fRRect = rrect; fInverted = false; } void setPath(const SkPath& path) { if (fType == Type::kPath) { // Assign directly fPath = path; } else { // In-place initialize this->setType(Type::kPath); new (&fPath) SkPath(path); } fInverted = path.isInverseFillType(); } void reset() { this->setType(Type::kEmpty); fInverted = false; } private: void setType(Type type) { if (this->isPath() && type != Type::kPath) { fPath.~SkPath(); } fType = type; } union { Rect fRect; // p0 = top-left, p1 = bot-right if type is kLine (may be unsorted) SkRRect fRRect; SkPath fPath; }; Type fType = Type::kEmpty; bool fInverted = false; }; } // namespace skgpu #endif // skgpu_geom_Shape_DEFINED