Revert "Reland "Revert "Add arcs as a specialized geometry to GrShape."""

This reverts commit 580aee2fa4.

Bug: skia:7794
Change-Id: I9c2b923859c826dff58c22c529dc4e2ab4d0f186
Reviewed-on: https://skia-review.googlesource.com/124042
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2018-04-26 15:22:04 -04:00 committed by Skia Commit-Bot
parent 8f379d4d64
commit e4949406eb
6 changed files with 297 additions and 20 deletions

View File

@ -1304,6 +1304,25 @@ 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;
#ifdef SK_DISABLE_ARC_TO_LINE_TO_CHECK
static constexpr bool kSkipLineToCheck = true;
#else
static constexpr bool kSkipLineToCheck = false;
#endif
if (forceMoveTo) {
this->moveTo(pt);
} else if (kSkipLineToCheck || !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 +1337,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 +1346,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);
}
}
@ -3343,6 +3362,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());
@ -3353,11 +3386,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) {
@ -3380,6 +3417,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);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -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:
*

View File

@ -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,

View File

@ -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,29 @@ void GrShape::addGenIDChangeListener(SkPathRef::GenIDChangeListener* listener) c
}
}
GrShape GrShape::MakeArc(const SkRect& oval, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees,
bool useCenter, const GrStyle& style) {
#ifdef SK_DISABLE_ARC_TO_LINE_TO_CHECK
// When this flag is set the segment mask of the path won't match GrShape's segment mask for
// paths. Represent this shape as a path.
SkPath path;
SkPathPriv::CreateDrawArcPath(&path, oval, startAngleDegrees, sweepAngleDegrees, useCenter,
style.isSimpleFill());
return GrShape(path, style);
#else
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;
#endif
}
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 +405,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 +636,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 +689,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);

View File

@ -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

View File

@ -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