/* * Copyright 2020 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/geometry/GrShape.h" #include "src/core/SkPathPriv.h" #include "src/core/SkRRectPriv.h" GrShape& GrShape::operator=(const GrShape& shape) { switch (shape.type()) { case Type::kEmpty: this->reset(); break; case Type::kPoint: this->setPoint(shape.fPoint); break; case Type::kRect: this->setRect(shape.fRect); break; case Type::kRRect: this->setRRect(shape.fRRect); break; case Type::kPath: this->setPath(shape.fPath); break; case Type::kArc: this->setArc(shape.fArc); break; case Type::kLine: this->setLine(shape.fLine); break; } fStart = shape.fStart; fCW = shape.fCW; fInverted = shape.fInverted; return *this; } uint32_t GrShape::stateKey() const { // Use the path's full fill type instead of just whether or not it's inverted. uint32_t key = this->isPath() ? static_cast(fPath.getFillType()) : (fInverted ? 1 : 0); key |= ((uint32_t) fType) << 2; // fill type was 2 bits key |= fStart << 5; // type was 3 bits, total 5 bits so far key |= (fCW ? 1 : 0) << 8; // start was 3 bits, total 8 bits so far return key; } bool GrShape::simplifyPath(unsigned flags) { SkASSERT(this->isPath()); SkRect rect; SkRRect rrect; SkPoint pts[2]; SkPathDirection dir; unsigned start; if (fPath.isEmpty()) { this->setType(Type::kEmpty); return false; } else if (fPath.isLine(pts)) { this->simplifyLine(pts[0], pts[1], flags); return false; } else if (SkPathPriv::IsRRect(fPath, &rrect, &dir, &start)) { this->simplifyRRect(rrect, dir, start, flags); return true; } else if (SkPathPriv::IsOval(fPath, &rect, &dir, &start)) { // Convert to rrect indexing since oval is not represented explicitly this->simplifyRRect(SkRRect::MakeOval(rect), dir, start * 2, flags); return true; } else if (SkPathPriv::IsSimpleRect(fPath, (flags & kSimpleFill_Flag), &rect, &dir, &start)) { // When there is a path effect we restrict rect detection to the narrower API that // gives us the starting position. Otherwise, we will retry with the more aggressive // isRect(). this->simplifyRect(rect, dir, start, flags); return true; } else if (flags & kIgnoreWinding_Flag) { // Attempt isRect() since we don't have to preserve any winding info bool closed; if (fPath.isRect(&rect, &closed) && (closed || (flags & kSimpleFill_Flag))) { this->simplifyRect(rect, kDefaultDir, kDefaultStart, flags); return true; } } // No further simplification for a path. For performance reasons, we don't query the path to // determine it was closed, as whether or not it was closed when it remains a path type is not // important for styling. return false; } bool GrShape::simplifyArc(unsigned flags) { SkASSERT(this->isArc()); // Arcs can simplify to rrects, lines, points, or empty; regardless of what it simplifies to // it was closed if went through the center point. bool wasClosed = fArc.fUseCenter; if (fArc.fOval.isEmpty() || !fArc.fSweepAngle) { if (flags & kSimpleFill_Flag) { // Go straight to empty, since the other degenerate shapes all have 0 area anyway. this->setType(Type::kEmpty); } else if (!fArc.fSweepAngle) { SkPoint center = {fArc.fOval.centerX(), fArc.fOval.centerY()}; SkScalar startRad = SkDegreesToRadians(fArc.fStartAngle); SkPoint start = {center.fX + 0.5f * fArc.fOval.width() * SkScalarCos(startRad), center.fY + 0.5f * fArc.fOval.height() * SkScalarSin(startRad)}; // Either just the starting point, or a line from the center to the start if (fArc.fUseCenter) { this->simplifyLine(center, start, flags); } else { this->simplifyPoint(start, flags); } } else { // TODO: Theoretically, we could analyze the arc projected into the empty bounds to // determine a line, but that is somewhat complex for little value (since the arc // can backtrack on itself if the sweep angle is large enough). this->setType(Type::kEmpty); } } else { if ((flags & kSimpleFill_Flag) || ((flags & kIgnoreWinding_Flag) && !fArc.fUseCenter)) { // Eligible to turn into an oval if it sweeps a full circle if (fArc.fSweepAngle <= -360.f || fArc.fSweepAngle >= 360.f) { this->simplifyRRect(SkRRect::MakeOval(fArc.fOval), kDefaultDir, kDefaultStart, flags); return true; } } if (flags & kMakeCanonical_Flag) { // Map start to 0 to 360, sweep is always positive if (fArc.fSweepAngle < 0) { fArc.fStartAngle = fArc.fStartAngle + fArc.fSweepAngle; fArc.fSweepAngle = -fArc.fSweepAngle; } if (fArc.fStartAngle < 0 || fArc.fStartAngle >= 360.f) { fArc.fStartAngle = SkScalarMod(fArc.fStartAngle, 360.f); } } } return wasClosed; } void GrShape::simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags) { if (rrect.isEmpty() || rrect.isRect()) { // Change index from rrect to rect start = ((start + 1) / 2) % 4; this->simplifyRect(rrect.rect(), dir, start, flags); } else if (!this->isRRect()) { this->setType(Type::kRRect); fRRect = rrect; this->setPathWindingParams(dir, start); // A round rect is already canonical, so there's nothing more to do } else { // If starting as a round rect, the provided rrect/winding params should be already set SkASSERT(fRRect == rrect && this->dir() == dir && this->startIndex() == start); } } void GrShape::simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags) { if (!rect.width() || !rect.height()) { if (flags & kSimpleFill_Flag) { // A zero area, filled shape so go straight to empty this->setType(Type::kEmpty); } else if (!rect.width() ^ !rect.height()) { // A line, choose the first point that best matches the starting index SkPoint p1 = {rect.fLeft, rect.fTop}; SkPoint p2 = {rect.fRight, rect.fBottom}; if (start >= 2 && !(flags & kIgnoreWinding_Flag)) { using std::swap; swap(p1, p2); } this->simplifyLine(p1, p2, flags); } else { // A point (all edges are equal, so start+dir doesn't affect choice) this->simplifyPoint({rect.fLeft, rect.fTop}, flags); } } else { if (!this->isRect()) { this->setType(Type::kRect); fRect = rect; this->setPathWindingParams(dir, start); } else { // If starting as a rect, the provided rect/winding params should already be set SkASSERT(fRect == rect && this->dir() == dir && this->startIndex() == start); } if (flags & kMakeCanonical_Flag) { fRect.sort(); } } } void GrShape::simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags) { if (flags & kSimpleFill_Flag) { this->setType(Type::kEmpty); } else if (p1 == p2) { this->simplifyPoint(p1, false); } else { if (!this->isLine()) { this->setType(Type::kLine); fLine.fP1 = p1; fLine.fP2 = p2; } else { // If starting as a line, the provided points should already be set SkASSERT(fLine.fP1 == p1 && fLine.fP2 == p2); } if (flags & kMakeCanonical_Flag) { // Sort the end points if (fLine.fP2.fY < fLine.fP1.fY || (fLine.fP2.fY == fLine.fP1.fY && fLine.fP2.fX < fLine.fP1.fX)) { using std::swap; swap(fLine.fP1, fLine.fP2); } } } } void GrShape::simplifyPoint(const SkPoint& point, unsigned flags) { if (flags & kSimpleFill_Flag) { this->setType(Type::kEmpty); } else if (!this->isPoint()) { this->setType(Type::kPoint); fPoint = point; } else { // If starting as a point, the provided position should already be set SkASSERT(point == fPoint); } } bool GrShape::simplify(unsigned flags) { // Verify that winding parameters are valid for the current type. SkASSERT((fType == Type::kRect || fType == Type::kRRect) || (this->dir() == kDefaultDir && this->startIndex() == kDefaultStart)); // The type specific functions automatically fall through to the simpler shapes, so // we only need to start in the right place. bool wasClosed = false; switch (fType) { case Type::kEmpty: // do nothing break; case Type::kPoint: this->simplifyPoint(fPoint, flags); break; case Type::kLine: this->simplifyLine(fLine.fP1, fLine.fP2, flags); break; case Type::kRect: this->simplifyRect(fRect, this->dir(), this->startIndex(), flags); wasClosed = true; break; case Type::kRRect: this->simplifyRRect(fRRect, this->dir(), this->startIndex(), flags); wasClosed = true; break; case Type::kPath: wasClosed = this->simplifyPath(flags); break; case Type::kArc: wasClosed = this->simplifyArc(flags); break; default: SkUNREACHABLE; } if (((flags & kIgnoreWinding_Flag) || (fType != Type::kRect && fType != Type::kRRect))) { // Reset winding parameters if we don't need them anymore this->setPathWindingParams(kDefaultDir, kDefaultStart); } return wasClosed; } bool GrShape::conservativeContains(const SkRect& rect) const { switch (this->type()) { case Type::kEmpty: case Type::kPoint: // fall through since a point has 0 area case Type::kLine: // fall through, "" (currently choosing not to test if 'rect' == line) return false; case Type::kRect: return fRect.contains(rect); case Type::kRRect: return fRRect.contains(rect); case Type::kPath: return fPath.conservativelyContainsRect(rect); case Type::kArc: if (fArc.fUseCenter) { SkPath arc; this->asPath(&arc); return arc.conservativelyContainsRect(rect); } else { return false; } } SkUNREACHABLE; } bool GrShape::conservativeContains(const SkPoint& point) const { switch (this->type()) { case Type::kEmpty: case Type::kPoint: // fall through, currently choosing not to test if shape == point case Type::kLine: // fall through, "" case Type::kArc: return false; case Type::kRect: return fRect.contains(point.fX, point.fY); case Type::kRRect: return SkRRectPriv::ContainsPoint(fRRect, point); case Type::kPath: return fPath.contains(point.fX, point.fY); } SkUNREACHABLE; } bool GrShape::closed() const { switch (this->type()) { case Type::kEmpty: // fall through case Type::kRect: // fall through case Type::kRRect: return true; case Type::kPath: // SkPath doesn't keep track of the closed status of each contour. return SkPathPriv::IsClosedSingleContour(fPath); case Type::kArc: return fArc.fUseCenter; case Type::kPoint: // fall through case Type::kLine: return false; } SkUNREACHABLE; } bool GrShape::convex(bool simpleFill) const { switch (this->type()) { case Type::kEmpty: // fall through case Type::kRect: // fall through case Type::kRRect: return true; case Type::kPath: // SkPath.isConvex() really means "is this path convex were it to be closed". // Convex paths may only have one contour hence isLastContourClosed() is sufficient. return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex(); case Type::kArc: return SkPathPriv::DrawArcIsConvex(fArc.fSweepAngle, fArc.fUseCenter, simpleFill); case Type::kPoint: // fall through case Type::kLine: return false; } SkUNREACHABLE; } SkRect GrShape::bounds() const { // Bounds where left == bottom or top == right can indicate a line or point shape. We return // inverted bounds for a truly empty shape. static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1); switch (this->type()) { case Type::kEmpty: return kInverted; case Type::kPoint: return {fPoint.fX, fPoint.fY, fPoint.fX, fPoint.fY}; case Type::kRect: return fRect.makeSorted(); case Type::kRRect: return fRRect.getBounds(); case Type::kPath: return fPath.getBounds(); case Type::kArc: return fArc.fOval; case Type::kLine: { SkRect b = SkRect::MakeLTRB(fLine.fP1.fX, fLine.fP1.fY, fLine.fP2.fX, fLine.fP2.fY); b.sort(); return b; } } SkUNREACHABLE; } uint32_t GrShape::segmentMask() const { // In order to match what a path would report, this has to inspect the shapes slightly // to reflect what they might simplify to. switch (this->type()) { case Type::kEmpty: return 0; case Type::kRRect: if (fRRect.isEmpty() || fRRect.isRect()) { return SkPath::kLine_SegmentMask; } else if (fRRect.isOval()) { return SkPath::kConic_SegmentMask; } else { return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask; } case Type::kPath: return fPath.getSegmentMasks(); case Type::kArc: if (fArc.fUseCenter) { return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask; } else { return SkPath::kConic_SegmentMask; } case Type::kPoint: // fall through case Type::kLine: // "" case Type::kRect: return SkPath::kLine_SegmentMask; } SkUNREACHABLE; } void GrShape::asPath(SkPath* out, bool simpleFill) const { if (!this->isPath() && !this->isArc()) { // When not a path, we need to set fill type on the path to match invertedness. // All the non-path geometries produce equivalent shapes with either even-odd or winding // so we can use the default fill type. out->reset(); out->setFillType(kDefaultFillType); if (fInverted) { out->toggleInverseFillType(); } } // Else when we're already a path, that will assign the fill type directly to 'out'. switch (this->type()) { case Type::kEmpty: return; case Type::kPoint: // A plain moveTo() or moveTo+close() does not match the expected path for a // point that is being dashed (see SkDashPath's handling of zero-length segments). out->moveTo(fPoint); out->lineTo(fPoint); return; case Type::kRect: out->addRect(fRect, this->dir(), this->startIndex()); return; case Type::kRRect: out->addRRect(fRRect, this->dir(), this->startIndex()); return; case Type::kPath: *out = fPath; return; case Type::kArc: SkPathPriv::CreateDrawArcPath(out, fArc.fOval, fArc.fStartAngle, fArc.fSweepAngle, fArc.fUseCenter, simpleFill); // CreateDrawArcPath resets the output path and configures its fill type, so we just // have to ensure invertedness is correct. if (fInverted) { out->toggleInverseFillType(); } return; case Type::kLine: out->moveTo(fLine.fP1); out->lineTo(fLine.fP2); return; } SkUNREACHABLE; }