| /* |
| * 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_graphite_geom_Shape_DEFINED |
| #define skgpu_graphite_geom_Shape_DEFINED |
| |
| #include "include/core/SkM44.h" |
| #include "include/core/SkPath.h" |
| #include "include/core/SkRRect.h" |
| #include "include/core/SkRect.h" |
| |
| #include "src/gpu/graphite/geom/Rect.h" |
| |
| #include <array> |
| |
| namespace skgpu::graphite { |
| |
| /** |
| * 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<int>(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(skvx::float2 p0, skvx::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(skvx::float2 point) 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. |
| skvx::float2 p0() const { SkASSERT(this->isLine()); return fRect.topLeft(); } |
| skvx::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(skvx::float2{p0.fX, p0.fY}, skvx::float2{p1.fX, p1.fY}); |
| } |
| void setLine(SkV2 p0, SkV2 p1) { |
| this->setLine(skvx::float2{p0.x, p0.y}, skvx::float2{p1.x, p1.y}); |
| } |
| void setLine(skvx::float2 p0, skvx::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::graphite |
| |
| #endif // skgpu_graphite_geom_Shape_DEFINED |