/*
|
* Copyright 2016 Google Inc.
|
*
|
* 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 "GrStyle.h"
|
#include "SkPath.h"
|
#include "SkPathPriv.h"
|
#include "SkRRect.h"
|
#include "SkTemplates.h"
|
#include "SkTLazy.h"
|
#include <new>
|
|
/**
|
* Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with.
|
* It is possible to apply the style to the GrShape to produce a new GrShape where the geometry
|
* reflects the styling information (e.g. is stroked). It is also possible to apply just the
|
* path effect from the style. In this case the resulting shape will include any remaining
|
* stroking information that is to be applied after the path effect.
|
*
|
* Shapes can produce keys that represent only the geometry information, not the style. Note that
|
* when styling information is applied to produce a new shape then the style has been converted
|
* to geometric information and is included in the new shape's key. When the same style is applied
|
* to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
|
* will be the same.
|
*
|
* Currently this can only be constructed from a path, rect, or rrect though it can become a path
|
* applying style to the geometry. The idea is to expand this to cover most or all of the geometries
|
* that have fast paths in the GPU backend.
|
*/
|
class GrShape {
|
public:
|
// Keys for paths may be extracted from the path data for small paths. Clients aren't supposed
|
// to have to worry about this. This value is exposed for unit tests.
|
static constexpr int kMaxKeyFromDataVerbCnt = 10;
|
|
GrShape() { this->initType(Type::kEmpty); }
|
|
explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {}
|
|
explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {}
|
|
explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {}
|
|
GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
|
this->initType(Type::kPath, &path);
|
this->attemptToSimplifyPath();
|
}
|
|
GrShape(const SkRRect& rrect, const GrStyle& style) : fStyle(style) {
|
this->initType(Type::kRRect);
|
fRRectData.fRRect = rrect;
|
fRRectData.fInverted = false;
|
fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(),
|
&fRRectData.fDir);
|
this->attemptToSimplifyRRect();
|
}
|
|
GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted,
|
const GrStyle& style)
|
: fStyle(style) {
|
this->initType(Type::kRRect);
|
fRRectData.fRRect = rrect;
|
fRRectData.fInverted = inverted;
|
if (style.pathEffect()) {
|
fRRectData.fDir = dir;
|
fRRectData.fStart = start;
|
if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
|
fRRectData.fStart = (fRRectData.fStart + 1) & 0b110;
|
} else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
|
fRRectData.fStart &= 0b110;
|
}
|
} else {
|
fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir);
|
}
|
this->attemptToSimplifyRRect();
|
}
|
|
GrShape(const SkRect& rect, const GrStyle& style) : fStyle(style) {
|
this->initType(Type::kRRect);
|
fRRectData.fRRect = SkRRect::MakeRect(rect);
|
fRRectData.fInverted = false;
|
fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
|
&fRRectData.fDir);
|
this->attemptToSimplifyRRect();
|
}
|
|
GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
|
this->initType(Type::kPath, &path);
|
this->attemptToSimplifyPath();
|
}
|
|
GrShape(const SkRRect& rrect, const SkPaint& paint) : fStyle(paint) {
|
this->initType(Type::kRRect);
|
fRRectData.fRRect = rrect;
|
fRRectData.fInverted = false;
|
fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
|
&fRRectData.fDir);
|
this->attemptToSimplifyRRect();
|
}
|
|
GrShape(const SkRect& rect, const SkPaint& paint) : fStyle(paint) {
|
this->initType(Type::kRRect);
|
fRRectData.fRRect = SkRRect::MakeRect(rect);
|
fRRectData.fInverted = false;
|
fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
|
&fRRectData.fDir);
|
this->attemptToSimplifyRRect();
|
}
|
|
static GrShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
|
SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
|
|
GrShape(const GrShape&);
|
GrShape& operator=(const GrShape& that);
|
|
~GrShape() { this->changeType(Type::kEmpty); }
|
|
/**
|
* Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
|
* version of the shape.
|
*/
|
enum class FillInversion {
|
kPreserve,
|
kFlip,
|
kForceNoninverted,
|
kForceInverted
|
};
|
/**
|
* Makes a filled shape from the pre-styled original shape and optionally modifies whether
|
* the fill is inverted or not. It's important to note that the original shape's geometry
|
* may already have been modified if doing so was neutral with respect to its style
|
* (e.g. filled paths are always closed when stored in a shape and dashed paths are always
|
* made non-inverted since dashing ignores inverseness).
|
*/
|
static GrShape MakeFilled(const GrShape& original, FillInversion = FillInversion::kPreserve);
|
|
const GrStyle& style() const { return fStyle; }
|
|
/**
|
* Returns a shape that has either applied the path effect or path effect and stroking
|
* information from this shape's style to its geometry. Scale is used when approximating the
|
* output geometry and typically is computed from the view matrix
|
*/
|
GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const {
|
return GrShape(*this, apply, scale);
|
}
|
|
bool isRect() const {
|
if (Type::kRRect != fType) {
|
return false;
|
}
|
|
return fRRectData.fRRect.isRect();
|
}
|
|
/** Returns the unstyled geometry as a rrect if possible. */
|
bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const {
|
if (Type::kRRect != fType) {
|
return false;
|
}
|
if (rrect) {
|
*rrect = fRRectData.fRRect;
|
}
|
if (dir) {
|
*dir = fRRectData.fDir;
|
}
|
if (start) {
|
*start = fRRectData.fStart;
|
}
|
if (inverted) {
|
*inverted = fRRectData.fInverted;
|
}
|
return true;
|
}
|
|
/**
|
* If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
|
* An inverse filled line path is still considered a line.
|
*/
|
bool asLine(SkPoint pts[2], bool* inverted) const {
|
if (fType != Type::kLine) {
|
return false;
|
}
|
if (pts) {
|
pts[0] = fLineData.fPts[0];
|
pts[1] = fLineData.fPts[1];
|
}
|
if (inverted) {
|
*inverted = fLineData.fInverted;
|
}
|
return true;
|
}
|
|
/** Returns the unstyled geometry as a path. */
|
void asPath(SkPath* out) const {
|
switch (fType) {
|
case Type::kEmpty:
|
out->reset();
|
break;
|
case Type::kInvertedEmpty:
|
out->reset();
|
out->setFillType(kDefaultPathInverseFillType);
|
break;
|
case Type::kRRect:
|
out->reset();
|
out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
|
// Below matches the fill type that attemptToSimplifyPath uses.
|
if (fRRectData.fInverted) {
|
out->setFillType(kDefaultPathInverseFillType);
|
} else {
|
out->setFillType(kDefaultPathFillType);
|
}
|
break;
|
case Type::kArc:
|
SkPathPriv::CreateDrawArcPath(out, fArcData.fOval, fArcData.fStartAngleDegrees,
|
fArcData.fSweepAngleDegrees, fArcData.fUseCenter,
|
fStyle.isSimpleFill());
|
if (fArcData.fInverted) {
|
out->setFillType(kDefaultPathInverseFillType);
|
} else {
|
out->setFillType(kDefaultPathFillType);
|
}
|
break;
|
case Type::kLine:
|
out->reset();
|
out->moveTo(fLineData.fPts[0]);
|
out->lineTo(fLineData.fPts[1]);
|
if (fLineData.fInverted) {
|
out->setFillType(kDefaultPathInverseFillType);
|
} else {
|
out->setFillType(kDefaultPathFillType);
|
}
|
break;
|
case Type::kPath:
|
*out = this->path();
|
break;
|
}
|
}
|
|
// Can this shape be drawn as a pair of filled nested rectangles?
|
bool asNestedRects(SkRect rects[2]) const {
|
if (Type::kPath != fType) {
|
return false;
|
}
|
|
// TODO: it would be better two store DRRects natively in the shape rather than converting
|
// them to a path and then reextracting the nested rects
|
if (this->path().isInverseFillType()) {
|
return false;
|
}
|
|
SkPath::Direction dirs[2];
|
if (!this->path().isNestedFillRects(rects, dirs)) {
|
return false;
|
}
|
|
if (SkPath::kWinding_FillType == this->path().getFillType() && dirs[0] == dirs[1]) {
|
// The two rects need to be wound opposite to each other
|
return false;
|
}
|
|
// Right now, nested rects where the margin is not the same width
|
// all around do not render correctly
|
const SkScalar* outer = rects[0].asScalars();
|
const SkScalar* inner = rects[1].asScalars();
|
|
bool allEq = true;
|
|
SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
|
bool allGoE1 = margin >= SK_Scalar1;
|
|
for (int i = 1; i < 4; ++i) {
|
SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
|
if (temp < SK_Scalar1) {
|
allGoE1 = false;
|
}
|
if (!SkScalarNearlyEqual(margin, temp)) {
|
allEq = false;
|
}
|
}
|
|
return allEq || allGoE1;
|
}
|
|
/**
|
* Returns whether the geometry is empty. Note that applying the style could produce a
|
* non-empty shape. It also may have an inverse fill.
|
*/
|
bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
|
|
/**
|
* Gets the bounds of the geometry without reflecting the shape's styling. This ignores
|
* the inverse fill nature of the geometry.
|
*/
|
SkRect bounds() const;
|
|
/**
|
* Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
|
* status).
|
*/
|
SkRect styledBounds() const;
|
|
/**
|
* Is this shape known to be convex, before styling is applied. An unclosed but otherwise
|
* convex path is considered to be closed if they styling reflects a fill and not otherwise.
|
* This is because filling closes all contours in the path.
|
*/
|
bool knownToBeConvex() const {
|
switch (fType) {
|
case Type::kEmpty:
|
return true;
|
case Type::kInvertedEmpty:
|
return true;
|
case Type::kRRect:
|
return true;
|
case Type::kArc:
|
return SkPathPriv::DrawArcIsConvex(fArcData.fSweepAngleDegrees,
|
SkToBool(fArcData.fUseCenter),
|
fStyle.isSimpleFill());
|
case Type::kLine:
|
return true;
|
case Type::kPath:
|
// SkPath.isConvex() really means "is this path convex were it to be closed" and
|
// thus doesn't give the correct answer for stroked paths, hence we also check
|
// whether the path is either filled or closed. Convex paths may only have one
|
// contour hence isLastContourClosed() is a sufficient for a convex path.
|
return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
|
this->path().isConvex();
|
}
|
return false;
|
}
|
|
/** Is the pre-styled geometry inverse filled? */
|
bool inverseFilled() const {
|
bool ret = false;
|
switch (fType) {
|
case Type::kEmpty:
|
ret = false;
|
break;
|
case Type::kInvertedEmpty:
|
ret = true;
|
break;
|
case Type::kRRect:
|
ret = fRRectData.fInverted;
|
break;
|
case Type::kArc:
|
ret = fArcData.fInverted;
|
break;
|
case Type::kLine:
|
ret = fLineData.fInverted;
|
break;
|
case Type::kPath:
|
ret = this->path().isInverseFillType();
|
break;
|
}
|
// Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
|
SkASSERT(!(ret && this->style().isDashed()));
|
return ret;
|
}
|
|
/**
|
* Might applying the styling to the geometry produce an inverse fill. The "may" part comes in
|
* because an arbitrary path effect could produce an inverse filled path. In other cases this
|
* can be thought of as "inverseFilledAfterStyling()".
|
*/
|
bool mayBeInverseFilledAfterStyling() const {
|
// An arbitrary path effect can produce an arbitrary output path, which may be inverse
|
// filled.
|
if (this->style().hasNonDashPathEffect()) {
|
return true;
|
}
|
return this->inverseFilled();
|
}
|
|
/**
|
* Is it known that the unstyled geometry has no unclosed contours. This means that it will
|
* not have any caps if stroked (modulo the effect of any path effect).
|
*/
|
bool knownToBeClosed() const {
|
switch (fType) {
|
case Type::kEmpty:
|
return true;
|
case Type::kInvertedEmpty:
|
return true;
|
case Type::kRRect:
|
return true;
|
case Type::kArc:
|
return fArcData.fUseCenter;
|
case Type::kLine:
|
return false;
|
case Type::kPath:
|
// SkPath doesn't keep track of the closed status of each contour.
|
return SkPathPriv::IsClosedSingleContour(this->path());
|
}
|
return false;
|
}
|
|
uint32_t segmentMask() const {
|
switch (fType) {
|
case Type::kEmpty:
|
return 0;
|
case Type::kInvertedEmpty:
|
return 0;
|
case Type::kRRect:
|
if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
|
return SkPath::kConic_SegmentMask;
|
} else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type ||
|
fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) {
|
return SkPath::kLine_SegmentMask;
|
}
|
return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
|
case Type::kArc:
|
if (fArcData.fUseCenter) {
|
return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
|
}
|
return SkPath::kConic_SegmentMask;
|
case Type::kLine:
|
return SkPath::kLine_SegmentMask;
|
case Type::kPath:
|
return this->path().getSegmentMasks();
|
}
|
return 0;
|
}
|
|
/**
|
* Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
|
* A negative value is returned if the shape has no key (shouldn't be cached).
|
*/
|
int unstyledKeySize() const;
|
|
bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
|
|
/**
|
* Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
|
* space allocated for the key and that unstyledKeySize() does not return a negative value
|
* for this shape.
|
*/
|
void writeUnstyledKey(uint32_t* key) const;
|
|
/**
|
* Adds a listener to the *original* path. Typically used to invalidate cached resources when
|
* a path is no longer in-use. If the shape started out as something other than a path, this
|
* does nothing.
|
*/
|
void addGenIDChangeListener(sk_sp<SkPathRef::GenIDChangeListener>) const;
|
|
/**
|
* Helpers that are only exposed for unit tests, to determine if the shape is a path, and get
|
* the generation ID of the *original* path. This is the path that will receive
|
* GenIDChangeListeners added to this shape.
|
*/
|
uint32_t testingOnly_getOriginalGenerationID() const;
|
bool testingOnly_isPath() const;
|
bool testingOnly_isNonVolatilePath() const;
|
|
private:
|
enum class Type {
|
kEmpty,
|
kInvertedEmpty,
|
kRRect,
|
kArc,
|
kLine,
|
kPath,
|
};
|
|
void initType(Type type, const SkPath* path = nullptr) {
|
fType = Type::kEmpty;
|
this->changeType(type, path);
|
}
|
|
void changeType(Type type, const SkPath* path = nullptr) {
|
bool wasPath = Type::kPath == fType;
|
fType = type;
|
bool isPath = Type::kPath == type;
|
SkASSERT(!path || isPath);
|
if (wasPath && !isPath) {
|
fPathData.fPath.~SkPath();
|
} else if (!wasPath && isPath) {
|
if (path) {
|
new (&fPathData.fPath) SkPath(*path);
|
} else {
|
new (&fPathData.fPath) SkPath();
|
}
|
} else if (isPath && path) {
|
fPathData.fPath = *path;
|
}
|
// Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
|
fPathData.fGenID = 0;
|
}
|
|
SkPath& path() {
|
SkASSERT(Type::kPath == fType);
|
return fPathData.fPath;
|
}
|
|
const SkPath& path() const {
|
SkASSERT(Type::kPath == fType);
|
return fPathData.fPath;
|
}
|
|
/** Constructor used by the applyStyle() function */
|
GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
|
|
/**
|
* Determines the key we should inherit from the input shape's geometry and style when
|
* we are applying the style to create a new shape.
|
*/
|
void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
|
|
void attemptToSimplifyPath();
|
void attemptToSimplifyRRect();
|
void attemptToSimplifyLine();
|
void attemptToSimplifyArc();
|
|
bool attemptToSimplifyStrokedLineToRRect();
|
|
/** Gets the path that gen id listeners should be added to. */
|
const SkPath* originalPathForListeners() const;
|
|
// Defaults to use when there is no distinction between even/odd and winding fills.
|
static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
|
static constexpr SkPath::FillType kDefaultPathInverseFillType =
|
SkPath::kInverseEvenOdd_FillType;
|
|
static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
|
static constexpr unsigned kDefaultRRectStart = 0;
|
|
static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
|
SkPath::Direction* dir) {
|
*dir = kDefaultRRectDir;
|
// This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
|
// beginning at index 0 (which happens to correspond to rrect index 0 or 7).
|
if (!hasPathEffect) {
|
// It doesn't matter what start we use, just be consistent to avoid redundant keys.
|
return kDefaultRRectStart;
|
}
|
// In SkPath a rect starts at index 0 by default. This is the top left corner. However,
|
// we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
|
// rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
|
bool swapX = rect.fLeft > rect.fRight;
|
bool swapY = rect.fTop > rect.fBottom;
|
if (swapX && swapY) {
|
// 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
|
return 2 * 2;
|
} else if (swapX) {
|
*dir = SkPath::kCCW_Direction;
|
// 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
|
return 2 * 1;
|
} else if (swapY) {
|
*dir = SkPath::kCCW_Direction;
|
// 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
|
return 2 * 3;
|
}
|
return 0;
|
}
|
|
static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
|
SkPath::Direction* dir) {
|
// This comes from SkPath's interface. The default for adding a SkRRect to a path is
|
// clockwise beginning at starting index 6.
|
static constexpr unsigned kPathRRectStartIdx = 6;
|
*dir = kDefaultRRectDir;
|
if (!hasPathEffect) {
|
// It doesn't matter what start we use, just be consistent to avoid redundant keys.
|
return kDefaultRRectStart;
|
}
|
return kPathRRectStartIdx;
|
}
|
|
union {
|
struct {
|
SkRRect fRRect;
|
SkPath::Direction fDir;
|
unsigned fStart;
|
bool fInverted;
|
} fRRectData;
|
struct {
|
SkRect fOval;
|
SkScalar fStartAngleDegrees;
|
SkScalar fSweepAngleDegrees;
|
int16_t fUseCenter;
|
int16_t fInverted;
|
} fArcData;
|
struct {
|
SkPath fPath;
|
// Gen ID of the original path (fPath may be modified)
|
int32_t fGenID;
|
} fPathData;
|
struct {
|
SkPoint fPts[2];
|
bool fInverted;
|
} fLineData;
|
};
|
GrStyle fStyle;
|
SkTLazy<SkPath> fInheritedPathForListeners;
|
SkAutoSTArray<8, uint32_t> fInheritedKey;
|
Type fType;
|
};
|
#endif
|