blob: d3c2bc0ed13c2fde93aa85b100cb540ba75f010b [file] [log] [blame]
/*
* 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 <array>
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<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(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