blob: 87dc2c7eb8902d313c747855c8921915b61afe1e [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ganesh/geometry/GrStyledShape.h"
#include "include/core/SkArc.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkPoint.h"
#include "include/core/SkStrokeRec.h"
#include "include/private/SkIDChangeListener.h"
#include "include/private/base/SkAlign.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkMalloc.h"
#include "include/private/base/SkTo.h"
#include <algorithm>
#include <cstring>
#include <utility>
GrStyledShape& GrStyledShape::operator=(const GrStyledShape& that) {
fShape = that.fShape;
fStyle = that.fStyle;
fGenID = that.fGenID;
fSimplified = that.fSimplified;
fInheritedKey.reset(that.fInheritedKey.count());
sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
sizeof(uint32_t) * fInheritedKey.count());
if (that.fInheritedPathForListeners.isValid()) {
fInheritedPathForListeners.set(*that.fInheritedPathForListeners);
} else {
fInheritedPathForListeners.reset();
}
return *this;
}
static bool is_inverted(bool originalIsInverted, GrStyledShape::FillInversion inversion) {
switch (inversion) {
case GrStyledShape::FillInversion::kPreserve:
return originalIsInverted;
case GrStyledShape::FillInversion::kFlip:
return !originalIsInverted;
case GrStyledShape::FillInversion::kForceInverted:
return true;
case GrStyledShape::FillInversion::kForceNoninverted:
return false;
}
return false;
}
GrStyledShape GrStyledShape::MakeFilled(const GrStyledShape& original, FillInversion inversion) {
bool newIsInverted = is_inverted(original.fShape.inverted(), inversion);
if (original.style().isSimpleFill() && newIsInverted == original.fShape.inverted()) {
// By returning the original rather than falling through we can preserve any inherited style
// key. Otherwise, we wipe it out below since the style change invalidates it.
return original;
}
GrStyledShape result;
SkASSERT(result.fStyle.isSimpleFill());
if (original.fInheritedPathForListeners.isValid()) {
result.fInheritedPathForListeners.set(*original.fInheritedPathForListeners);
}
result.fShape = original.fShape;
result.fGenID = original.fGenID;
result.fShape.setInverted(newIsInverted);
if (!original.style().isSimpleFill()) {
// Going from a non-filled style to fill may allow additional simplifications (e.g.
// closing an open rect that wasn't closed in the original shape because it had
// stroke style).
result.simplify();
// The above simplify() call only sets simplified to true if its geometry was changed,
// since it already sees its style as a simple fill. Since the original style was not a
// simple fill, MakeFilled always simplifies.
result.fSimplified = true;
}
// Verify that lines/points were converted to empty by the style change
SkASSERT((!original.fShape.isLine() && !original.fShape.isPoint()) || result.fShape.isEmpty());
// We don't copy the inherited key since it can contain path effect information that we just
// stripped.
return result;
}
SkRect GrStyledShape::styledBounds() const {
if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) {
return SkRect::MakeEmpty();
}
SkRect bounds;
fStyle.adjustBounds(&bounds, this->bounds());
return bounds;
}
// If the path is small enough to be keyed from its data this returns key length, otherwise -1.
static int path_key_from_data_size(const SkPath& path) {
const int verbCnt = path.countVerbs();
if (verbCnt > GrStyledShape::kMaxKeyFromDataVerbCnt) {
return -1;
}
const int pointCnt = path.countPoints();
const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
static_assert(sizeof(SkScalar) == sizeof(uint32_t));
// 1 is for the verb count. Each verb is a byte but we'll pad the verb data out to
// a uint32_t length.
return 1 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
}
// Writes the path data key into the passed pointer.
static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
uint32_t* key = origKey;
// The check below should take care of negative values casted positive.
const int verbCnt = path.countVerbs();
const int pointCnt = path.countPoints();
const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
SkASSERT(verbCnt <= GrStyledShape::kMaxKeyFromDataVerbCnt);
SkASSERT(pointCnt && verbCnt);
*key++ = verbCnt;
memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
int verbKeySize = SkAlign4(verbCnt);
// pad out to uint32_t alignment using value that will stand out when debugging.
uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbCnt;
memset(pad, 0xDE, verbKeySize - verbCnt);
key += verbKeySize >> 2;
memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt);
static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
key += 2 * pointCnt;
sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt);
static_assert(sizeof(SkScalar) == sizeof(uint32_t));
SkDEBUGCODE(key += conicWeightCnt);
SkASSERT(key - origKey == path_key_from_data_size(path));
}
int GrStyledShape::unstyledKeySize() const {
if (fInheritedKey.count()) {
return fInheritedKey.count();
}
int count = 1; // Every key has the state flags from the GrShape
switch(fShape.type()) {
case GrShape::Type::kPoint:
static_assert(0 == sizeof(SkPoint) % sizeof(uint32_t));
count += sizeof(SkPoint) / sizeof(uint32_t);
break;
case GrShape::Type::kRect:
static_assert(0 == sizeof(SkRect) % sizeof(uint32_t));
count += sizeof(SkRect) / sizeof(uint32_t);
break;
case GrShape::Type::kRRect:
static_assert(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
count += SkRRect::kSizeInMemory / sizeof(uint32_t);
break;
case GrShape::Type::kArc:
static_assert(0 == sizeof(SkArc) % sizeof(uint32_t));
count += sizeof(SkArc) / sizeof(uint32_t);
break;
case GrShape::Type::kLine:
static_assert(0 == sizeof(GrLineSegment) % sizeof(uint32_t));
count += sizeof(GrLineSegment) / sizeof(uint32_t);
break;
case GrShape::Type::kPath: {
if (0 == fGenID) {
return -1; // volatile, so won't be keyed
}
int dataKeySize = path_key_from_data_size(fShape.path());
if (dataKeySize >= 0) {
count += dataKeySize;
} else {
count++; // Just adds the gen ID.
}
break; }
default:
// else it's empty, which just needs the state flags for its key
SkASSERT(fShape.isEmpty());
}
return count;
}
void GrStyledShape::writeUnstyledKey(uint32_t* key) const {
SkASSERT(this->unstyledKeySize());
SkDEBUGCODE(uint32_t* origKey = key;)
if (fInheritedKey.count()) {
memcpy(key, fInheritedKey.get(), sizeof(uint32_t) * fInheritedKey.count());
SkDEBUGCODE(key += fInheritedKey.count();)
} else {
// Dir and start are only used for rect and rrect shapes, so are not included in other
// shape type keys. Make sure that they are the defaults for other shapes so it doesn't
// matter that we universally include them in the flag key value.
SkASSERT((fShape.isRect() || fShape.isRRect()) ||
(fShape.dir() == GrShape::kDefaultDir &&
fShape.startIndex() == GrShape::kDefaultStart));
// Every key starts with the state from the GrShape (this includes path fill type,
// and any tracked winding, start, inversion, as well as the class of geometry).
*key++ = fShape.stateKey();
switch(fShape.type()) {
case GrShape::Type::kPath: {
SkASSERT(fGenID != 0);
// Ensure that the path's inversion matches our state so that the path's key suffices.
SkASSERT(fShape.inverted() == fShape.path().isInverseFillType());
int dataKeySize = path_key_from_data_size(fShape.path());
if (dataKeySize >= 0) {
write_path_key_from_data(fShape.path(), key);
return;
} else {
*key++ = fGenID;
}
break; }
case GrShape::Type::kPoint:
memcpy(key, &fShape.point(), sizeof(SkPoint));
key += sizeof(SkPoint) / sizeof(uint32_t);
break;
case GrShape::Type::kRect:
memcpy(key, &fShape.rect(), sizeof(SkRect));
key += sizeof(SkRect) / sizeof(uint32_t);
break;
case GrShape::Type::kRRect:
fShape.rrect().writeToMemory(key);
key += SkRRect::kSizeInMemory / sizeof(uint32_t);
break;
case GrShape::Type::kArc:
// Write dense floats first
memcpy(key, &fShape.arc(), sizeof(SkRect) + 2 * sizeof(float));
key += (sizeof(SkArc) / sizeof(uint32_t) - 1);
// Then write the final bool as an int, to make sure upper bits are set
*key++ = fShape.arc().isWedge() ? 1 : 0;
break;
case GrShape::Type::kLine:
memcpy(key, &fShape.line(), sizeof(GrLineSegment));
key += sizeof(GrLineSegment) / sizeof(uint32_t);
break;
default:
// Nothing other than the flag state is needed in the key for an empty shape
SkASSERT(fShape.isEmpty());
}
}
SkASSERT(key - origKey == this->unstyledKeySize());
}
void GrStyledShape::setInheritedKey(const GrStyledShape &parent, GrStyle::Apply apply,
SkScalar scale) {
SkASSERT(!fInheritedKey.count());
// If the output shape turns out to be simple, then we will just use its geometric key
if (fShape.isPath()) {
// We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
// ApplyFullStyle(shape).
// The full key is structured as (geo,path_effect,stroke).
// If we do ApplyPathEffect we get geo,path_effect as the inherited key. If we then
// do ApplyFullStyle we'll memcpy geo,path_effect into the new inherited key
// and then append the style key (which should now be stroke only) at the end.
int parentCnt = parent.fInheritedKey.count();
bool useParentGeoKey = !parentCnt;
if (useParentGeoKey) {
parentCnt = parent.unstyledKeySize();
if (parentCnt < 0) {
// The parent's geometry has no key so we will have no key.
fGenID = 0;
return;
}
}
uint32_t styleKeyFlags = 0;
if (parent.knownToBeClosed()) {
styleKeyFlags |= GrStyle::kClosed_KeyFlag;
}
if (parent.asLine(nullptr, nullptr)) {
styleKeyFlags |= GrStyle::kNoJoins_KeyFlag;
}
int styleCnt = GrStyle::KeySize(parent.fStyle, apply, styleKeyFlags);
if (styleCnt < 0) {
// The style doesn't allow a key, set the path gen ID to 0 so that we fail when
// we try to get a key for the shape.
fGenID = 0;
return;
}
fInheritedKey.reset(parentCnt + styleCnt);
if (useParentGeoKey) {
// This will be the geo key.
parent.writeUnstyledKey(fInheritedKey.get());
} else {
// This should be (geo,path_effect).
memcpy(fInheritedKey.get(), parent.fInheritedKey.get(),
parentCnt * sizeof(uint32_t));
}
// Now turn (geo,path_effect) or (geo) into (geo,path_effect,stroke)
GrStyle::WriteKey(fInheritedKey.get() + parentCnt, parent.fStyle, apply, scale,
styleKeyFlags);
}
}
const SkPath* GrStyledShape::originalPathForListeners() const {
if (fInheritedPathForListeners.isValid()) {
return fInheritedPathForListeners.get();
} else if (fShape.isPath() && !fShape.path().isVolatile()) {
return &fShape.path();
}
return nullptr;
}
void GrStyledShape::addGenIDChangeListener(sk_sp<SkIDChangeListener> listener) const {
if (const auto* lp = this->originalPathForListeners()) {
SkPathPriv::AddGenIDChangeListener(*lp, std::move(listener));
}
}
GrStyledShape GrStyledShape::MakeArc(const SkArc& arc,
const GrStyle& style,
DoSimplify doSimplify) {
GrStyledShape result;
result.fShape.setArc(
SkArc::Make(arc.fOval.makeSorted(), arc.fStartAngle, arc.fSweepAngle, arc.fType));
result.fStyle = style;
if (doSimplify == DoSimplify::kYes) {
result.simplify();
}
return result;
}
GrStyledShape::GrStyledShape(const GrStyledShape& that)
: fShape(that.fShape)
, fStyle(that.fStyle)
, fGenID(that.fGenID)
, fSimplified(that.fSimplified) {
fInheritedKey.reset(that.fInheritedKey.count());
sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
sizeof(uint32_t) * fInheritedKey.count());
if (that.fInheritedPathForListeners.isValid()) {
fInheritedPathForListeners.set(*that.fInheritedPathForListeners);
}
}
GrStyledShape::GrStyledShape(const GrStyledShape& parent, GrStyle::Apply apply, SkScalar scale) {
// TODO: Add some quantization of scale for better cache performance here or leave that up
// to caller?
// TODO: For certain shapes and stroke params we could ignore the scale. (e.g. miter or bevel
// stroke of a rect).
if (!parent.style().applies() ||
(GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) {
*this = parent;
return;
}
SkPathEffect* pe = parent.fStyle.pathEffect();
SkTLazy<SkPath> tmpPath;
const GrStyledShape* parentForKey = &parent;
SkTLazy<GrStyledShape> tmpParent;
// Start out as an empty path that is filled in by the applied style
fShape.setPath(SkPath());
if (pe) {
const SkPath* srcForPathEffect;
if (parent.fShape.isPath()) {
srcForPathEffect = &parent.fShape.path();
} else {
srcForPathEffect = tmpPath.init();
parent.asPath(tmpPath.get());
}
// Should we consider bounds? Would have to include in key, but it'd be nice to know
// if the bounds actually modified anything before including in key.
SkStrokeRec strokeRec = parent.fStyle.strokeRec();
if (!parent.fStyle.applyPathEffectToPath(&fShape.path(), &strokeRec, *srcForPathEffect,
scale)) {
tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr));
*this = tmpParent->applyStyle(apply, scale);
return;
}
// A path effect has access to change the res scale but we aren't expecting it to and it
// would mess up our key computation.
SkASSERT(scale == strokeRec.getResScale());
if (GrStyle::Apply::kPathEffectAndStrokeRec == apply && strokeRec.needToApply()) {
// The intermediate shape may not be a general path. If we we're just applying
// the path effect then attemptToReduceFromPath would catch it. This means that
// when we subsequently applied the remaining strokeRec we would have a non-path
// parent shape that would be used to determine the the stroked path's key.
// We detect that case here and change parentForKey to a temporary that represents
// the simpler shape so that applying both path effect and the strokerec all at
// once produces the same key.
tmpParent.init(fShape.path(), GrStyle(strokeRec, nullptr));
tmpParent->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale);
if (!tmpPath.isValid()) {
tmpPath.init();
}
tmpParent->asPath(tmpPath.get());
SkStrokeRec::InitStyle fillOrHairline;
// The parent shape may have simplified away the strokeRec, check for that here.
if (tmpParent->style().applies()) {
SkAssertResult(tmpParent.get()->style().applyToPath(&fShape.path(), &fillOrHairline,
*tmpPath.get(), scale));
} else if (tmpParent->style().isSimpleFill()) {
fillOrHairline = SkStrokeRec::kFill_InitStyle;
} else {
SkASSERT(tmpParent.get()->style().isSimpleHairline());
fillOrHairline = SkStrokeRec::kHairline_InitStyle;
}
fStyle.resetToInitStyle(fillOrHairline);
parentForKey = tmpParent.get();
} else {
fStyle = GrStyle(strokeRec, nullptr);
}
} else {
const SkPath* srcForParentStyle;
if (parent.fShape.isPath()) {
srcForParentStyle = &parent.fShape.path();
} else {
srcForParentStyle = tmpPath.init();
parent.asPath(tmpPath.get());
}
SkStrokeRec::InitStyle fillOrHairline;
SkASSERT(parent.fStyle.applies());
SkASSERT(!parent.fStyle.pathEffect());
SkAssertResult(parent.fStyle.applyToPath(&fShape.path(), &fillOrHairline,
*srcForParentStyle, scale));
fStyle.resetToInitStyle(fillOrHairline);
}
if (parent.fInheritedPathForListeners.isValid()) {
fInheritedPathForListeners.set(*parent.fInheritedPathForListeners);
} else if (parent.fShape.isPath() && !parent.fShape.path().isVolatile()) {
fInheritedPathForListeners.set(parent.fShape.path());
}
this->simplify();
this->setInheritedKey(*parentForKey, apply, scale);
}
bool GrStyledShape::asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start,
bool* inverted) const {
if (!fShape.isRRect() && !fShape.isRect()) {
return false;
}
// Validity check here, if we don't have a path effect on the style, we should have passed
// appropriate flags to GrShape::simplify() to have reset these parameters.
SkASSERT(fStyle.hasPathEffect() || (fShape.dir() == GrShape::kDefaultDir &&
fShape.startIndex() == GrShape::kDefaultStart));
// If the shape is a regular rect, map to round rect winding parameters, including accounting
// for the automatic sorting of edges that SkRRect::MakeRect() performs.
if (fShape.isRect()) {
if (rrect) {
*rrect = SkRRect::MakeRect(fShape.rect());
}
// Don't bother mapping these if we don't have a path effect, however.
if (!fStyle.hasPathEffect()) {
if (dir) {
*dir = GrShape::kDefaultDir;
}
if (start) {
*start = GrShape::kDefaultStart;
}
} else {
// 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 and direction.
SkPathDirection rectDir = fShape.dir();
unsigned rectStart = fShape.startIndex();
if (fShape.rect().fLeft > fShape.rect().fRight) {
// Toggle direction, and modify index by mapping through the array
static const unsigned kMapping[] = {1, 0, 3, 2};
rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
: SkPathDirection::kCCW;
rectStart = kMapping[rectStart];
}
if (fShape.rect().fTop > fShape.rect().fBottom) {
// Toggle direction and map index by 3 - start
// NOTE: if we earlier flipped for X as well, this results in no net direction
// change and effectively flipping the start index to the diagonal corners of the
// rect (matching what we'd expect for a rect with both X and Y flipped).
rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
: SkPathDirection::kCCW;
rectStart = 3 - rectStart;
}
if (dir) {
*dir = rectDir;
}
if (start) {
// Convert to round rect indexing
*start = 2 * rectStart;
}
}
} else {
// Straight forward export
if (rrect) {
*rrect = fShape.rrect();
}
if (dir) {
*dir = fShape.dir();
}
if (start) {
*start = fShape.startIndex();
// Canonicalize the index if the rrect is an oval, which GrShape doesn't treat special
// but we do for dashing placement
if (fShape.rrect().isOval()) {
*start &= 0b110;
}
}
}
if (inverted) {
*inverted = fShape.inverted();
}
return true;
}
bool GrStyledShape::asLine(SkPoint pts[2], bool* inverted) const {
if (!fShape.isLine()) {
return false;
}
if (pts) {
pts[0] = fShape.line().fP1;
pts[1] = fShape.line().fP2;
}
if (inverted) {
*inverted = fShape.inverted();
}
return true;
}
bool GrStyledShape::asNestedRects(SkRect rects[2]) const {
if (!fShape.isPath()) {
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 (fShape.path().isInverseFillType()) {
return false;
}
SkPathDirection dirs[2];
if (!SkPathPriv::IsNestedFillRects(fShape.path(), rects, dirs)) {
return false;
}
if (SkPathFillType::kWinding == fShape.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;
}
class AutoRestoreInverseness {
public:
AutoRestoreInverseness(GrShape* shape, const GrStyle& style)
// Dashing ignores inverseness skbug.com/5421.
: fShape(shape), fInverted(!style.isDashed() && fShape->inverted()) {}
~AutoRestoreInverseness() {
// Restore invertedness after any modifications were made to the shape type
fShape->setInverted(fInverted);
SkASSERT(!fShape->isPath() || fInverted == fShape->path().isInverseFillType());
}
private:
GrShape* fShape;
bool fInverted;
};
void GrStyledShape::simplify() {
AutoRestoreInverseness ari(&fShape, fStyle);
unsigned simplifyFlags = 0;
if (fStyle.isSimpleFill()) {
simplifyFlags = GrShape::kAll_Flags;
} else if (!fStyle.hasPathEffect()) {
// Everything but arcs with caps that might extend beyond the oval edge can ignore winding
if (!fShape.isArc() || fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
simplifyFlags |= GrShape::kIgnoreWinding_Flag;
}
simplifyFlags |= GrShape::kMakeCanonical_Flag;
} // else if there's a path effect, every destructive simplification is disabledd
// Remember if the original shape was closed; in the event we simplify to a point or line
// because of degenerate geometry, we need to update joins and caps.
GrShape::Type oldType = fShape.type();
fClosed = fShape.simplify(simplifyFlags);
fSimplified = oldType != fShape.type();
if (fShape.isPath()) {
// The shape remains a path, so configure the gen ID and canonicalize fill type if possible
if (fInheritedKey.count() || fShape.path().isVolatile()) {
fGenID = 0;
} else {
fGenID = fShape.path().getGenerationID();
}
if (!fStyle.hasNonDashPathEffect() &&
(fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
fStyle.strokeRec().getStyle() == SkStrokeRec::kHairline_Style ||
fShape.path().isConvex())) {
// Stroke styles don't differentiate between winding and even/odd. There is no
// distinction between even/odd and non-zero winding count for convex paths.
// Moreover, dashing ignores inverseness (skbug.com/5421)
fShape.path().setFillType(GrShape::kDefaultFillType);
}
} else {
fInheritedKey.reset(0);
// Whenever we simplify to a non-path, break the chain so we no longer refer to the
// original path. This prevents attaching genID listeners to temporary paths created when
// drawing simple shapes.
fInheritedPathForListeners.reset();
// Further simplifications to the shape based on the style
this->simplifyStroke();
}
}
void GrStyledShape::simplifyStroke() {
AutoRestoreInverseness ari(&fShape, fStyle);
// For stroke+filled rects, a mitered shape becomes a larger rect and a rounded shape
// becomes a round rect.
if (!fStyle.hasPathEffect() && fShape.isRect() &&
fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
if (fStyle.strokeRec().getJoin() == SkPaint::kBevel_Join ||
(fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
fStyle.strokeRec().getMiter() < SK_ScalarSqrt2)) {
// Bevel-stroked rect needs path rendering
return;
}
SkScalar r = fStyle.strokeRec().getWidth() / 2;
fShape.rect().outset(r, r);
if (fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
// There's no dashing to worry about if we got here, so it's okay that this resets
// winding parameters
fShape.setRRect(SkRRect::MakeRectXY(fShape.rect(), r, r));
}
fStyle = GrStyle::SimpleFill();
fSimplified = true;
return;
}
// Otherwise, if we're a point or a line, we might be able to explicitly apply some of the
// stroking (and even some of the dashing). Any other shape+style is too complicated to reduce.
if ((!fShape.isPoint() && !fShape.isLine()) || fStyle.hasNonDashPathEffect() ||
fStyle.strokeRec().isHairlineStyle()) {
return;
}
// Tracks style simplifications, even if the geometry can't be further simplified.
bool styleSimplified = false;
if (fStyle.isDashed()) {
// For dashing a point, if the first interval is on, we can drop the dash and just draw
// the caps. For dashing a line, if every off interval is 0 length, its a stroke.
bool dropDash = false;
if (fShape.isPoint()) {
dropDash = fStyle.dashIntervalCnt() > 0 &&
SkToBool(fStyle.dashIntervals()[0]);
} else {
dropDash = true;
for (int i = 1; i < fStyle.dashIntervalCnt(); i += 2) {
if (SkToBool(fStyle.dashIntervals()[i])) {
// An off interval has non-zero length so this won't convert to a simple line
dropDash = false;
break;
}
}
}
if (!dropDash) {
return;
}
// Fall through to modifying the shape to respect the new stroke geometry
fStyle = GrStyle(fStyle.strokeRec(), nullptr);
// Since the reduced the line or point after dashing is dependent on the caps of the dashes,
// we reset to be unclosed so we don't override the style based on joins later.
fClosed = false;
styleSimplified = true;
}
// At this point, we're a line or point with no path effects. Any fill portion of the style
// is empty, so a fill-only style can be empty, and a stroke+fill becomes a stroke.
if (fStyle.isSimpleFill()) {
fShape.reset();
fSimplified = true;
return;
} else if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
// Stroke only
SkStrokeRec rec = fStyle.strokeRec();
rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false);
fStyle = GrStyle(rec, nullptr);
styleSimplified = true;
}
// A point or line that was formed by a degenerate closed shape needs its style updated to
// reflect the fact that it doesn't actually produce caps.
if (fClosed) {
SkPaint::Cap cap;
if (fShape.isLine() && fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
// As a closed shape, the line moves from a to b and back to a, producing a 180 degree
// turn. With round joins, this would make a semi-circle at each end, which is visually
// identical to a round cap on the reduced line geometry.
cap = SkPaint::kRound_Cap;
} else {
// If this were a closed line, the 180 degree turn either is a miter join that exceeds
// the miter limit and becomes a bevel, or a bevel join. In either case, the bevel shape
// of a 180 degreen corner is equivalent to a butt cap.
// - to match the SVG spec, the 0-length sides of an empty rectangle are skipped, so
// it fits this closed line description (it is not two 90 degree turns that could
// produce miter geometry).
cap = SkPaint::kButt_Cap;
}
if (cap != fStyle.strokeRec().getCap() ||
SkPaint::kDefault_Join != fStyle.strokeRec().getJoin()) {
SkStrokeRec rec = fStyle.strokeRec();
rec.setStrokeParams(cap, SkPaint::kDefault_Join, fStyle.strokeRec().getMiter());
fStyle = GrStyle(rec, nullptr);
styleSimplified = true;
}
}
if (fShape.isPoint()) {
// The drawn geometry is entirely based on the cap style and stroke width. A butt cap point
// doesn't draw anything, a round cap is an oval and a square cap is a square.
if (fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
fShape.reset();
} else {
SkScalar w = fStyle.strokeRec().getWidth() / 2.f;
SkRect r = {fShape.point().fX, fShape.point().fY, fShape.point().fX, fShape.point().fY};
r.outset(w, w);
if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
fShape.setRRect(SkRRect::MakeOval(r));
} else {
fShape.setRect(r);
}
}
} else {
// Stroked lines reduce to rectangles or round rects when they are axis-aligned. If we
// allowed rotation angle, this would work for any lines.
SkRect rect;
SkVector outset;
if (fShape.line().fP1.fY == fShape.line().fP2.fY) {
rect.fLeft = std::min(fShape.line().fP1.fX, fShape.line().fP2.fX);
rect.fRight = std::max(fShape.line().fP1.fX, fShape.line().fP2.fX);
rect.fTop = rect.fBottom = fShape.line().fP1.fY;
outset.fY = fStyle.strokeRec().getWidth() / 2.f;
outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY;
} else if (fShape.line().fP1.fX == fShape.line().fP2.fX) {
rect.fTop = std::min(fShape.line().fP1.fY, fShape.line().fP2.fY);
rect.fBottom = std::max(fShape.line().fP1.fY, fShape.line().fP2.fY);
rect.fLeft = rect.fRight = fShape.line().fP1.fX;
outset.fX = fStyle.strokeRec().getWidth() / 2.f;
outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX;
} else {
// Geometrically can't apply the style and turn into a fill, but might still be simpler
// than before based solely on changes to fStyle.
fSimplified |= styleSimplified;
return;
}
rect.outset(outset.fX, outset.fY);
if (rect.isEmpty()) {
fShape.reset();
} else if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
SkASSERT(outset.fX == outset.fY);
fShape.setRRect(SkRRect::MakeRectXY(rect, outset.fX, outset.fY));
} else {
fShape.setRect(rect);
}
}
// If we made it here, the stroke was fully applied to the new shape so we can become a fill.
fStyle = GrStyle::SimpleFill();
fSimplified = true;
}