Add arcs as a specialized geometry to GrShape.
BUG: skia:7794 Change-Id: I484693711f48e55631732a0f4ee97e2848dec89d Reviewed-on: https://skia-review.googlesource.com/122900 Commit-Queue: Brian Salomon <bsalomon@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
3b8feb331a
commit
255bcf57ff
@ -1304,6 +1304,19 @@ void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
|
||||
|
||||
SkPoint singlePt;
|
||||
|
||||
// Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
|
||||
// close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
|
||||
// arcs from the same oval.
|
||||
auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
|
||||
SkPoint lastPt;
|
||||
if (forceMoveTo) {
|
||||
this->moveTo(pt);
|
||||
} else if (!this->getLastPt(&lastPt) || !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
|
||||
!SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
|
||||
this->lineTo(pt);
|
||||
}
|
||||
};
|
||||
|
||||
// At this point, we know that the arc is not a lone point, but startV == stopV
|
||||
// indicates that the sweepAngle is too small such that angles_to_unit_vectors
|
||||
// cannot handle it.
|
||||
@ -1318,7 +1331,7 @@ void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
|
||||
// make sin(endAngle) to be 0 which will then draw a dot.
|
||||
singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
|
||||
oval.centerY() + radiusY * sk_float_sin(endAngle));
|
||||
forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
|
||||
addPt(singlePt);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1327,12 +1340,12 @@ void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
|
||||
if (count) {
|
||||
this->incReserve(count * 2 + 1);
|
||||
const SkPoint& pt = conics[0].fPts[0];
|
||||
forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
|
||||
addPt(pt);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
|
||||
}
|
||||
} else {
|
||||
forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
|
||||
addPt(singlePt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3339,6 +3352,20 @@ bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Di
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
|
||||
if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
|
||||
// This gets converted to an oval.
|
||||
return true;
|
||||
}
|
||||
if (useCenter) {
|
||||
// This is a pie wedge. It's convex if the angle is <= 180.
|
||||
return SkScalarAbs(sweepAngle) <= 180.f;
|
||||
}
|
||||
// When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
|
||||
// to a secant, i.e. convex.
|
||||
return SkScalarAbs(sweepAngle) <= 360.f;
|
||||
}
|
||||
|
||||
void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
|
||||
SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
|
||||
SkASSERT(!oval.isEmpty());
|
||||
@ -3349,11 +3376,15 @@ void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar st
|
||||
path->setFillType(SkPath::kWinding_FillType);
|
||||
if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
|
||||
path->addOval(oval);
|
||||
SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
|
||||
return;
|
||||
}
|
||||
if (useCenter) {
|
||||
path->moveTo(oval.centerX(), oval.centerY());
|
||||
}
|
||||
auto firstDir =
|
||||
sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
|
||||
bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
|
||||
// Arc to mods at 360 and drawArc is not supposed to.
|
||||
bool forceMoveTo = !useCenter;
|
||||
while (sweepAngle <= -360.f) {
|
||||
@ -3376,6 +3407,8 @@ void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar st
|
||||
if (useCenter) {
|
||||
path->close();
|
||||
}
|
||||
path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
|
||||
path->fFirstDirection.store(firstDir);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -105,6 +105,12 @@ public:
|
||||
static void CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
|
||||
SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect);
|
||||
|
||||
/**
|
||||
* Determines whether an arc produced by CreateDrawArcPath will be convex. Assumes a non-empty
|
||||
* oval.
|
||||
*/
|
||||
static bool DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect);
|
||||
|
||||
/**
|
||||
* Returns a C++11-iterable object that traverses a path's verbs in order. e.g:
|
||||
*
|
||||
|
@ -136,6 +136,7 @@ void SkStrokeRec::applyToPaint(SkPaint* paint) const {
|
||||
}
|
||||
|
||||
static inline SkScalar get_inflation_bounds(SkPaint::Join join,
|
||||
SkPaint::Cap cap,
|
||||
SkScalar strokeWidth,
|
||||
SkScalar miterLimit) {
|
||||
if (strokeWidth < 0) { // fill
|
||||
@ -145,20 +146,25 @@ static inline SkScalar get_inflation_bounds(SkPaint::Join join,
|
||||
}
|
||||
// since we're stroked, outset the rect by the radius (and join type)
|
||||
SkScalar radius = SkScalarHalf(strokeWidth);
|
||||
SkScalar inflation = radius;
|
||||
if (SkPaint::kMiter_Join == join) {
|
||||
if (miterLimit > SK_Scalar1) {
|
||||
radius *= miterLimit;
|
||||
inflation *= miterLimit;
|
||||
}
|
||||
}
|
||||
return radius;
|
||||
// A square cap at a 45 degree angle can add sqrt(2)*radius.
|
||||
if (SkPaint::kSquare_Cap == cap) {
|
||||
inflation = SkTMax(inflation, radius * SK_ScalarSqrt2);
|
||||
}
|
||||
return inflation;
|
||||
}
|
||||
|
||||
SkScalar SkStrokeRec::getInflationRadius() const {
|
||||
return get_inflation_bounds((SkPaint::Join)fJoin, fWidth, fMiterLimit);
|
||||
return get_inflation_bounds((SkPaint::Join)fJoin, (SkPaint::Cap)fCap, fWidth, fMiterLimit);
|
||||
}
|
||||
|
||||
SkScalar SkStrokeRec::GetInflationRadius(const SkPaint& paint, SkPaint::Style style) {
|
||||
SkScalar width = SkPaint::kFill_Style == style ? -SK_Scalar1 : paint.getStrokeWidth();
|
||||
return get_inflation_bounds(paint.getStrokeJoin(), width, paint.getStrokeMiter());
|
||||
|
||||
return get_inflation_bounds(paint.getStrokeJoin(), paint.getStrokeCap(), width,
|
||||
paint.getStrokeMiter());
|
||||
}
|
||||
|
@ -1353,10 +1353,9 @@ void GrRenderTargetContext::drawArc(const GrClip& clip,
|
||||
return;
|
||||
}
|
||||
}
|
||||
SkPath path;
|
||||
SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter,
|
||||
style.isSimpleFill());
|
||||
this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrShape(path, style));
|
||||
this->drawShapeUsingPathRenderer(
|
||||
clip, std::move(paint), aa, viewMatrix,
|
||||
GrShape::MakeArc(oval, startAngle, sweepAngle, useCenter, style));
|
||||
}
|
||||
|
||||
void GrRenderTargetContext::drawImageLattice(const GrClip& clip,
|
||||
|
@ -18,6 +18,9 @@ GrShape& GrShape::operator=(const GrShape& that) {
|
||||
case Type::kRRect:
|
||||
fRRectData = that.fRRectData;
|
||||
break;
|
||||
case Type::kArc:
|
||||
fArcData = that.fArcData;
|
||||
break;
|
||||
case Type::kLine:
|
||||
fLineData = that.fLineData;
|
||||
break;
|
||||
@ -82,6 +85,14 @@ GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) {
|
||||
result.fRRectData.fStart = kDefaultRRectStart;
|
||||
result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion);
|
||||
break;
|
||||
case Type::kArc:
|
||||
result.fType = original.fType;
|
||||
result.fArcData.fOval = original.fArcData.fOval;
|
||||
result.fArcData.fStartAngleDegrees = original.fArcData.fStartAngleDegrees;
|
||||
result.fArcData.fSweepAngleDegrees = original.fArcData.fSweepAngleDegrees;
|
||||
result.fArcData.fUseCenter = original.fArcData.fUseCenter;
|
||||
result.fArcData.fInverted = is_inverted(original.fArcData.fInverted, inversion);
|
||||
break;
|
||||
case Type::kLine:
|
||||
// Lines don't fill.
|
||||
if (is_inverted(original.fLineData.fInverted, inversion)) {
|
||||
@ -144,6 +155,9 @@ SkRect GrShape::bounds() const {
|
||||
}
|
||||
case Type::kRRect:
|
||||
return fRRectData.fRRect.getBounds();
|
||||
case Type::kArc:
|
||||
// Could make this less conservative by looking at angles.
|
||||
return fArcData.fOval;
|
||||
case Type::kPath:
|
||||
return this->path().getBounds();
|
||||
}
|
||||
@ -215,9 +229,13 @@ int GrShape::unstyledKeySize() const {
|
||||
return 1;
|
||||
case Type::kRRect:
|
||||
SkASSERT(!fInheritedKey.count());
|
||||
SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
|
||||
GR_STATIC_ASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
|
||||
// + 1 for the direction, start index, and inverseness.
|
||||
return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
|
||||
case Type::kArc:
|
||||
SkASSERT(!fInheritedKey.count());
|
||||
GR_STATIC_ASSERT(0 == sizeof(fArcData) % sizeof(uint32_t));
|
||||
return sizeof(fArcData) / sizeof(uint32_t);
|
||||
case Type::kLine:
|
||||
GR_STATIC_ASSERT(2 * sizeof(uint32_t) == sizeof(SkPoint));
|
||||
// 4 for the end points and 1 for the inverseness
|
||||
@ -260,6 +278,10 @@ void GrShape::writeUnstyledKey(uint32_t* key) const {
|
||||
*key++ |= fRRectData.fStart;
|
||||
SkASSERT(fRRectData.fStart < 8);
|
||||
break;
|
||||
case Type::kArc:
|
||||
memcpy(key, &fArcData, sizeof(fArcData));
|
||||
key += sizeof(fArcData) / sizeof(uint32_t);
|
||||
break;
|
||||
case Type::kLine:
|
||||
memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint));
|
||||
key += 4;
|
||||
@ -349,6 +371,20 @@ void GrShape::addGenIDChangeListener(SkPathRef::GenIDChangeListener* listener) c
|
||||
}
|
||||
}
|
||||
|
||||
GrShape GrShape::MakeArc(const SkRect& oval, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees,
|
||||
bool useCenter, const GrStyle& style) {
|
||||
GrShape result;
|
||||
result.changeType(Type::kArc);
|
||||
result.fArcData.fOval = oval;
|
||||
result.fArcData.fStartAngleDegrees = startAngleDegrees;
|
||||
result.fArcData.fSweepAngleDegrees = sweepAngleDegrees;
|
||||
result.fArcData.fUseCenter = useCenter;
|
||||
result.fArcData.fInverted = false;
|
||||
result.fStyle = style;
|
||||
result.attemptToSimplifyArc();
|
||||
return result;
|
||||
}
|
||||
|
||||
GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) {
|
||||
const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr;
|
||||
this->initType(that.fType, thatPath);
|
||||
@ -360,6 +396,9 @@ GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) {
|
||||
case Type::kRRect:
|
||||
fRRectData = that.fRRectData;
|
||||
break;
|
||||
case Type::kArc:
|
||||
fArcData = that.fArcData;
|
||||
break;
|
||||
case Type::kLine:
|
||||
fLineData = that.fLineData;
|
||||
break;
|
||||
@ -588,6 +627,7 @@ void GrShape::attemptToSimplifyRRect() {
|
||||
} else if (fStyle.isDashed()) {
|
||||
// Dashing ignores the inverseness (currently). skbug.com/5421
|
||||
fRRectData.fInverted = false;
|
||||
// Possible TODO here: Check whether the dash results in a single arc or line.
|
||||
}
|
||||
// Turn a stroke-and-filled miter rect into a filled rect. TODO: more rrect stroke shortcuts.
|
||||
if (!fStyle.hasPathEffect() &&
|
||||
@ -640,6 +680,46 @@ void GrShape::attemptToSimplifyLine() {
|
||||
}
|
||||
}
|
||||
|
||||
void GrShape::attemptToSimplifyArc() {
|
||||
SkASSERT(fType == Type::kArc);
|
||||
SkASSERT(!fArcData.fInverted);
|
||||
if (fArcData.fOval.isEmpty() || !fArcData.fSweepAngleDegrees) {
|
||||
this->changeType(Type::kEmpty);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assuming no path effect, a filled, stroked, hairline, or stroke-and-filled arc that traverses
|
||||
// the full circle and doesn't use the center point is an oval. Unless it has square or round
|
||||
// caps. They may protrude out of the oval. Round caps can't protrude out of a circle but we're
|
||||
// ignoring that for now.
|
||||
if (fStyle.isSimpleFill() || (!fStyle.pathEffect() && !fArcData.fUseCenter &&
|
||||
fStyle.strokeRec().getCap() == SkPaint::kButt_Cap)) {
|
||||
if (fArcData.fSweepAngleDegrees >= 360.f || fArcData.fSweepAngleDegrees <= -360.f) {
|
||||
auto oval = fArcData.fOval;
|
||||
this->changeType(Type::kRRect);
|
||||
this->fRRectData.fRRect.setOval(oval);
|
||||
this->fRRectData.fDir = kDefaultRRectDir;
|
||||
this->fRRectData.fStart = kDefaultRRectStart;
|
||||
this->fRRectData.fInverted = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!fStyle.pathEffect()) {
|
||||
// Canonicalize the arc such that the start is always in [0, 360) and the sweep is always
|
||||
// positive.
|
||||
if (fArcData.fSweepAngleDegrees < 0) {
|
||||
fArcData.fStartAngleDegrees = fArcData.fStartAngleDegrees + fArcData.fSweepAngleDegrees;
|
||||
fArcData.fSweepAngleDegrees = -fArcData.fSweepAngleDegrees;
|
||||
}
|
||||
}
|
||||
if (this->fArcData.fStartAngleDegrees < 0 || this->fArcData.fStartAngleDegrees >= 360.f) {
|
||||
this->fArcData.fStartAngleDegrees = SkScalarMod(this->fArcData.fStartAngleDegrees, 360.f);
|
||||
}
|
||||
// Possible TODOs here: Look at whether dash pattern results in a single dash and convert to
|
||||
// non-dashed stroke. Stroke and fill can be fill if circular and no path effect. Just stroke
|
||||
// could as well if the stroke fills the center.
|
||||
}
|
||||
|
||||
bool GrShape::attemptToSimplifyStrokedLineToRRect() {
|
||||
SkASSERT(Type::kLine == fType);
|
||||
SkASSERT(fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style);
|
||||
|
@ -116,6 +116,9 @@ public:
|
||||
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);
|
||||
|
||||
@ -209,6 +212,16 @@ public:
|
||||
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]);
|
||||
@ -256,6 +269,10 @@ public:
|
||||
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:
|
||||
@ -282,6 +299,9 @@ public:
|
||||
case Type::kRRect:
|
||||
ret = fRRectData.fInverted;
|
||||
break;
|
||||
case Type::kArc:
|
||||
ret = fArcData.fInverted;
|
||||
break;
|
||||
case Type::kLine:
|
||||
ret = fLineData.fInverted;
|
||||
break;
|
||||
@ -320,6 +340,8 @@ public:
|
||||
return true;
|
||||
case Type::kRRect:
|
||||
return true;
|
||||
case Type::kArc:
|
||||
return fArcData.fUseCenter;
|
||||
case Type::kLine:
|
||||
return false;
|
||||
case Type::kPath:
|
||||
@ -343,6 +365,11 @@ public:
|
||||
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:
|
||||
@ -387,6 +414,7 @@ private:
|
||||
kEmpty,
|
||||
kInvertedEmpty,
|
||||
kRRect,
|
||||
kArc,
|
||||
kLine,
|
||||
kPath,
|
||||
};
|
||||
@ -438,6 +466,7 @@ private:
|
||||
void attemptToSimplifyPath();
|
||||
void attemptToSimplifyRRect();
|
||||
void attemptToSimplifyLine();
|
||||
void attemptToSimplifyArc();
|
||||
|
||||
bool attemptToSimplifyStrokedLineToRRect();
|
||||
|
||||
@ -494,26 +523,33 @@ private:
|
||||
return kPathRRectStartIdx;
|
||||
}
|
||||
|
||||
Type fType;
|
||||
union {
|
||||
struct {
|
||||
SkRRect fRRect;
|
||||
SkPath::Direction fDir;
|
||||
unsigned fStart;
|
||||
bool fInverted;
|
||||
SkRRect fRRect;
|
||||
SkPath::Direction fDir;
|
||||
unsigned fStart;
|
||||
bool fInverted;
|
||||
} fRRectData;
|
||||
struct {
|
||||
SkPath fPath;
|
||||
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;
|
||||
int32_t fGenID;
|
||||
} fPathData;
|
||||
struct {
|
||||
SkPoint fPts[2];
|
||||
bool fInverted;
|
||||
SkPoint fPts[2];
|
||||
bool fInverted;
|
||||
} fLineData;
|
||||
};
|
||||
GrStyle fStyle;
|
||||
SkTLazy<SkPath> fInheritedPathForListeners;
|
||||
GrStyle fStyle;
|
||||
SkTLazy<SkPath> fInheritedPathForListeners;
|
||||
SkAutoSTArray<8, uint32_t> fInheritedKey;
|
||||
Type fType;
|
||||
};
|
||||
#endif
|
||||
|
@ -60,7 +60,7 @@ static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds)
|
||||
// everything got clipped out.
|
||||
static constexpr int kRes = 2000;
|
||||
// This tolerance is in units of 1/kRes fractions of the bounds width/height.
|
||||
static constexpr int kTol = 0;
|
||||
static constexpr int kTol = 2;
|
||||
GR_STATIC_ASSERT(kRes % 4 == 0);
|
||||
SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
|
||||
sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
|
||||
@ -389,6 +389,34 @@ private:
|
||||
SkRRect fRRect;
|
||||
};
|
||||
|
||||
class ArcGeo : public Geo {
|
||||
public:
|
||||
ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
|
||||
: fOval(oval)
|
||||
, fStartAngle(startAngle)
|
||||
, fSweepAngle(sweepAngle)
|
||||
, fUseCenter(useCenter) {}
|
||||
|
||||
SkPath path() const override {
|
||||
SkPath path;
|
||||
SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
|
||||
return path;
|
||||
}
|
||||
|
||||
GrShape makeShape(const SkPaint& paint) const override {
|
||||
return GrShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
|
||||
}
|
||||
|
||||
// GrShape specializes when created from arc params but it doesn't recognize arcs from SkPath.
|
||||
bool isNonPath(const SkPaint& paint) const override { return false; }
|
||||
|
||||
private:
|
||||
SkRect fOval;
|
||||
SkScalar fStartAngle;
|
||||
SkScalar fSweepAngle;
|
||||
bool fUseCenter;
|
||||
};
|
||||
|
||||
class PathGeo : public Geo {
|
||||
public:
|
||||
enum class Invert { kNo, kYes };
|
||||
@ -2121,6 +2149,10 @@ DEF_TEST(GrShape, reporter) {
|
||||
PathGeo::Invert::kNo));
|
||||
}
|
||||
|
||||
// Arcs
|
||||
geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
|
||||
geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
|
||||
|
||||
{
|
||||
SkPath openRectPath;
|
||||
openRectPath.moveTo(0, 0);
|
||||
@ -2221,4 +2253,80 @@ DEF_TEST(GrShape, reporter) {
|
||||
test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo));
|
||||
}
|
||||
|
||||
DEF_TEST(GrShape_arcs, reporter) {
|
||||
SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
|
||||
roundStroke.setStrokeStyle(2.f);
|
||||
roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
|
||||
|
||||
SkStrokeRec squareStroke(roundStroke);
|
||||
squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
|
||||
|
||||
SkStrokeRec roundStrokeAndFill(roundStroke);
|
||||
roundStrokeAndFill.setStrokeStyle(2.f, true);
|
||||
|
||||
static constexpr SkScalar kIntervals[] = {1, 2};
|
||||
auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
|
||||
|
||||
SkTArray<GrStyle> styles;
|
||||
styles.push_back(GrStyle::SimpleFill());
|
||||
styles.push_back(GrStyle::SimpleHairline());
|
||||
styles.push_back(GrStyle(roundStroke, nullptr));
|
||||
styles.push_back(GrStyle(squareStroke, nullptr));
|
||||
styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
|
||||
styles.push_back(GrStyle(roundStroke, dash));
|
||||
|
||||
for (const auto& style : styles) {
|
||||
// An empty rect never draws anything according to SkCanvas::drawArc() docs.
|
||||
TestCase emptyArc(GrShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style), reporter);
|
||||
TestCase emptyPath(reporter, SkPath(), style);
|
||||
emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
|
||||
|
||||
static constexpr SkRect kOval1{0, 0, 50, 50};
|
||||
static constexpr SkRect kOval2{50, 0, 100, 50};
|
||||
// Test that swapping starting and ending angle doesn't change the shape unless the arc
|
||||
// has a path effect. Also test that different ovals produce different shapes.
|
||||
TestCase arc1CW(GrShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
|
||||
TestCase arc1CCW(GrShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
|
||||
|
||||
TestCase arc1CWWithCenter(GrShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
|
||||
TestCase arc1CCWWithCenter(GrShape::MakeArc(kOval1, 90.f, -90.f, true, style), reporter);
|
||||
|
||||
TestCase arc2CW(GrShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
|
||||
TestCase arc2CWWithCenter(GrShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
|
||||
|
||||
auto reversedExepectations = style.hasPathEffect()
|
||||
? TestCase::kAllDifferent_ComparisonExpecation
|
||||
: TestCase::kAllSame_ComparisonExpecation;
|
||||
arc1CW.compare(reporter, arc1CCW, reversedExepectations);
|
||||
arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
|
||||
arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
|
||||
arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
|
||||
arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
|
||||
TestCase::kAllDifferent_ComparisonExpecation);
|
||||
|
||||
// Test that two arcs that start at the same angle but specified differently are equivalent.
|
||||
TestCase arc3A(GrShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
|
||||
TestCase arc3B(GrShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
|
||||
arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
|
||||
|
||||
// Test that an arc that traverses the entire oval (and then some) is equivalent to the
|
||||
// oval itself unless there is a path effect.
|
||||
TestCase ovalArc(GrShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
|
||||
TestCase oval(GrShape(SkRRect::MakeOval(kOval1)), reporter);
|
||||
auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
|
||||
: TestCase::kAllSame_ComparisonExpecation;
|
||||
if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
|
||||
ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
|
||||
}
|
||||
ovalArc.compare(reporter, oval, ovalExpectations);
|
||||
|
||||
// If the the arc starts/ends at the center then it is then equivalent to the oval only for
|
||||
// simple fills.
|
||||
TestCase ovalArcWithCenter(GrShape::MakeArc(kOval1, 304.f, 1225.f, true, style), reporter);
|
||||
ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
|
||||
: TestCase::kAllDifferent_ComparisonExpecation;
|
||||
ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user