| /* |
| * 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/ganesh/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<uint32_t>(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; |
| } |