| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/entity/geometry/stroke_path_geometry.h" |
| |
| #include "impeller/core/buffer_view.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/entity/geometry/geometry.h" |
| #include "impeller/geometry/constants.h" |
| #include "impeller/geometry/path_builder.h" |
| #include "impeller/geometry/path_component.h" |
| #include "impeller/geometry/separated_vector.h" |
| |
| namespace impeller { |
| using VS = SolidFillVertexShader; |
| |
| namespace { |
| |
| /// @brief The minimum stroke size can be less than one physical pixel because |
| /// of MSAA, but no less that half a physical pixel otherwise we might |
| /// not hit one of the sample positions. |
| static constexpr Scalar kMinStrokeSizeMSAA = 0.5f; |
| |
| static constexpr Scalar kMinStrokeSize = 1.0f; |
| |
| template <typename VertexWriter> |
| using CapProc = std::function<void(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& offset, |
| Scalar scale, |
| bool reverse)>; |
| |
| template <typename VertexWriter> |
| using JoinProc = std::function<void(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& start_offset, |
| const Point& end_offset, |
| Scalar miter_limit, |
| Scalar scale)>; |
| |
| class PositionWriter { |
| public: |
| void AppendVertex(const Point& point) { |
| data_.emplace_back(SolidFillVertexShader::PerVertexData{.position = point}); |
| } |
| |
| const std::vector<SolidFillVertexShader::PerVertexData>& GetData() const { |
| return data_; |
| } |
| |
| private: |
| std::vector<SolidFillVertexShader::PerVertexData> data_ = {}; |
| }; |
| |
| template <typename VertexWriter> |
| class StrokeGenerator { |
| public: |
| StrokeGenerator(const Path::Polyline& p_polyline, |
| const Scalar p_stroke_width, |
| const Scalar p_scaled_miter_limit, |
| const JoinProc<VertexWriter>& p_join_proc, |
| const CapProc<VertexWriter>& p_cap_proc, |
| const Scalar p_scale) |
| : polyline(p_polyline), |
| stroke_width(p_stroke_width), |
| scaled_miter_limit(p_scaled_miter_limit), |
| join_proc(p_join_proc), |
| cap_proc(p_cap_proc), |
| scale(p_scale) {} |
| |
| void Generate(VertexWriter& vtx_builder) { |
| for (size_t contour_i = 0; contour_i < polyline.contours.size(); |
| contour_i++) { |
| const Path::PolylineContour& contour = polyline.contours[contour_i]; |
| size_t contour_start_point_i, contour_end_point_i; |
| std::tie(contour_start_point_i, contour_end_point_i) = |
| polyline.GetContourPointBounds(contour_i); |
| |
| auto contour_delta = contour_end_point_i - contour_start_point_i; |
| if (contour_delta == 1) { |
| Point p = polyline.GetPoint(contour_start_point_i); |
| cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale, |
| /*reverse=*/false); |
| cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, scale, |
| /*reverse=*/false); |
| continue; |
| } else if (contour_delta == 0) { |
| continue; // This contour has no renderable content. |
| } |
| |
| previous_offset = offset; |
| offset = ComputeOffset(contour_start_point_i, contour_start_point_i, |
| contour_end_point_i, contour); |
| const Point contour_first_offset = offset.GetVector(); |
| |
| if (contour_i > 0) { |
| // This branch only executes when we've just finished drawing a contour |
| // and are switching to a new one. |
| // We're drawing a triangle strip, so we need to "pick up the pen" by |
| // appending two vertices at the end of the previous contour and two |
| // vertices at the start of the new contour (thus connecting the two |
| // contours with two zero volume triangles, which will be discarded by |
| // the rasterizer). |
| vtx.position = polyline.GetPoint(contour_start_point_i - 1); |
| // Append two vertices when "picking up" the pen so that the triangle |
| // drawn when moving to the beginning of the new contour will have zero |
| // volume. |
| vtx_builder.AppendVertex(vtx.position); |
| vtx_builder.AppendVertex(vtx.position); |
| |
| vtx.position = polyline.GetPoint(contour_start_point_i); |
| // Append two vertices at the beginning of the new contour, which |
| // appends two triangles of zero area. |
| vtx_builder.AppendVertex(vtx.position); |
| vtx_builder.AppendVertex(vtx.position); |
| } |
| |
| // Generate start cap. |
| if (!polyline.contours[contour_i].is_closed) { |
| Point cap_offset = |
| Vector2(-contour.start_direction.y, contour.start_direction.x) * |
| stroke_width * 0.5f; // Counterclockwise normal |
| cap_proc(vtx_builder, polyline.GetPoint(contour_start_point_i), |
| cap_offset, scale, /*reverse=*/true); |
| } |
| |
| for (size_t contour_component_i = 0; |
| contour_component_i < contour.components.size(); |
| contour_component_i++) { |
| const Path::PolylineContour::Component& component = |
| contour.components[contour_component_i]; |
| bool is_last_component = |
| contour_component_i == contour.components.size() - 1; |
| |
| size_t component_start_index = component.component_start_index; |
| size_t component_end_index = |
| is_last_component ? contour_end_point_i - 1 |
| : contour.components[contour_component_i + 1] |
| .component_start_index; |
| if (component.is_curve) { |
| AddVerticesForCurveComponent( |
| vtx_builder, component_start_index, component_end_index, |
| contour_start_point_i, contour_end_point_i, contour); |
| } else { |
| AddVerticesForLinearComponent( |
| vtx_builder, component_start_index, component_end_index, |
| contour_start_point_i, contour_end_point_i, contour); |
| } |
| } |
| |
| // Generate end cap or join. |
| if (!contour.is_closed) { |
| auto cap_offset = |
| Vector2(-contour.end_direction.y, contour.end_direction.x) * |
| stroke_width * 0.5f; // Clockwise normal |
| cap_proc(vtx_builder, polyline.GetPoint(contour_end_point_i - 1), |
| cap_offset, scale, /*reverse=*/false); |
| } else { |
| join_proc(vtx_builder, polyline.GetPoint(contour_start_point_i), |
| offset.GetVector(), contour_first_offset, scaled_miter_limit, |
| scale); |
| } |
| } |
| } |
| |
| /// Computes offset by calculating the direction from point_i - 1 to point_i |
| /// if point_i is within `contour_start_point_i` and `contour_end_point_i`; |
| /// Otherwise, it uses direction from contour. |
| SeparatedVector2 ComputeOffset(const size_t point_i, |
| const size_t contour_start_point_i, |
| const size_t contour_end_point_i, |
| const Path::PolylineContour& contour) const { |
| Point direction; |
| if (point_i >= contour_end_point_i) { |
| direction = contour.end_direction; |
| } else if (point_i <= contour_start_point_i) { |
| direction = -contour.start_direction; |
| } else { |
| direction = (polyline.GetPoint(point_i) - polyline.GetPoint(point_i - 1)) |
| .Normalize(); |
| } |
| return SeparatedVector2(Vector2{-direction.y, direction.x}, |
| stroke_width * 0.5f); |
| } |
| |
| void AddVerticesForLinearComponent(VertexWriter& vtx_builder, |
| const size_t component_start_index, |
| const size_t component_end_index, |
| const size_t contour_start_point_i, |
| const size_t contour_end_point_i, |
| const Path::PolylineContour& contour) { |
| bool is_last_component = component_start_index == |
| contour.components.back().component_start_index; |
| |
| for (size_t point_i = component_start_index; point_i < component_end_index; |
| point_i++) { |
| bool is_end_of_component = point_i == component_end_index - 1; |
| |
| Point offset_vector = offset.GetVector(); |
| |
| vtx.position = polyline.GetPoint(point_i) + offset_vector; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = polyline.GetPoint(point_i) - offset_vector; |
| vtx_builder.AppendVertex(vtx.position); |
| |
| // For line components, two additional points need to be appended |
| // prior to appending a join connecting the next component. |
| vtx.position = polyline.GetPoint(point_i + 1) + offset_vector; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = polyline.GetPoint(point_i + 1) - offset_vector; |
| vtx_builder.AppendVertex(vtx.position); |
| |
| previous_offset = offset; |
| offset = ComputeOffset(point_i + 2, contour_start_point_i, |
| contour_end_point_i, contour); |
| if (!is_last_component && is_end_of_component) { |
| // Generate join from the current line to the next line. |
| join_proc(vtx_builder, polyline.GetPoint(point_i + 1), |
| previous_offset.GetVector(), offset.GetVector(), |
| scaled_miter_limit, scale); |
| } |
| } |
| } |
| |
| void AddVerticesForCurveComponent(VertexWriter& vtx_builder, |
| const size_t component_start_index, |
| const size_t component_end_index, |
| const size_t contour_start_point_i, |
| const size_t contour_end_point_i, |
| const Path::PolylineContour& contour) { |
| bool is_last_component = component_start_index == |
| contour.components.back().component_start_index; |
| |
| for (size_t point_i = component_start_index; point_i < component_end_index; |
| point_i++) { |
| bool is_end_of_component = point_i == component_end_index - 1; |
| |
| vtx.position = polyline.GetPoint(point_i) + offset.GetVector(); |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = polyline.GetPoint(point_i) - offset.GetVector(); |
| vtx_builder.AppendVertex(vtx.position); |
| |
| previous_offset = offset; |
| offset = ComputeOffset(point_i + 2, contour_start_point_i, |
| contour_end_point_i, contour); |
| |
| // If the angle to the next segment is too sharp, round out the join. |
| if (!is_end_of_component) { |
| constexpr Scalar kAngleThreshold = 10 * kPi / 180; |
| // `std::cosf` is not constexpr-able, unfortunately, so we have to bake |
| // the alignment constant. |
| constexpr Scalar kAlignmentThreshold = |
| 0.984807753012208; // std::cosf(kThresholdAngle) -- 10 degrees |
| |
| // Use a cheap dot product to determine whether the angle is too sharp. |
| if (previous_offset.GetAlignment(offset) < kAlignmentThreshold) { |
| Scalar angle_total = previous_offset.AngleTo(offset).radians; |
| Scalar angle = kAngleThreshold; |
| |
| // Bridge the large angle with additional geometry at |
| // `kAngleThreshold` interval. |
| while (angle < std::abs(angle_total)) { |
| Scalar signed_angle = angle_total < 0 ? -angle : angle; |
| Point offset = |
| previous_offset.GetVector().Rotate(Radians(signed_angle)); |
| vtx.position = polyline.GetPoint(point_i) + offset; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = polyline.GetPoint(point_i) - offset; |
| vtx_builder.AppendVertex(vtx.position); |
| |
| angle += kAngleThreshold; |
| } |
| } |
| } |
| |
| // For curve components, the polyline is detailed enough such that |
| // it can avoid worrying about joins altogether. |
| if (is_end_of_component) { |
| // Append two additional vertices to close off the component. If we're |
| // on the _last_ component of the contour then we need to use the |
| // contour's end direction. |
| // `ComputeOffset` returns the contour's end direction when attempting |
| // to grab offsets past `contour_end_point_i`, so just use `offset` when |
| // we're on the last component. |
| Point last_component_offset = is_last_component |
| ? offset.GetVector() |
| : previous_offset.GetVector(); |
| vtx.position = polyline.GetPoint(point_i + 1) + last_component_offset; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = polyline.GetPoint(point_i + 1) - last_component_offset; |
| vtx_builder.AppendVertex(vtx.position); |
| // Generate join from the current line to the next line. |
| if (!is_last_component) { |
| join_proc(vtx_builder, polyline.GetPoint(point_i + 1), |
| previous_offset.GetVector(), offset.GetVector(), |
| scaled_miter_limit, scale); |
| } |
| } |
| } |
| } |
| |
| const Path::Polyline& polyline; |
| const Scalar stroke_width; |
| const Scalar scaled_miter_limit; |
| const JoinProc<VertexWriter>& join_proc; |
| const CapProc<VertexWriter>& cap_proc; |
| const Scalar scale; |
| |
| SeparatedVector2 previous_offset; |
| SeparatedVector2 offset; |
| SolidFillVertexShader::PerVertexData vtx; |
| }; |
| |
| template <typename VertexWriter> |
| void CreateButtCap(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& offset, |
| Scalar scale, |
| bool reverse) { |
| Point orientation = offset * (reverse ? -1 : 1); |
| VS::PerVertexData vtx; |
| vtx.position = position + orientation; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = position - orientation; |
| vtx_builder.AppendVertex(vtx.position); |
| } |
| |
| template <typename VertexWriter> |
| void CreateRoundCap(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& offset, |
| Scalar scale, |
| bool reverse) { |
| Point orientation = offset * (reverse ? -1 : 1); |
| Point forward(offset.y, -offset.x); |
| Point forward_normal = forward.Normalize(); |
| |
| CubicPathComponent arc; |
| if (reverse) { |
| arc = CubicPathComponent( |
| forward, forward + orientation * PathBuilder::kArcApproximationMagic, |
| orientation + forward * PathBuilder::kArcApproximationMagic, |
| orientation); |
| } else { |
| arc = CubicPathComponent( |
| orientation, |
| orientation + forward * PathBuilder::kArcApproximationMagic, |
| forward + orientation * PathBuilder::kArcApproximationMagic, forward); |
| } |
| |
| Point vtx = position + orientation; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position - orientation; |
| vtx_builder.AppendVertex(vtx); |
| |
| arc.ToLinearPathComponents(scale, [&vtx_builder, &vtx, forward_normal, |
| position](const Point& point) { |
| vtx = position + point; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position + (-point).Reflect(forward_normal); |
| vtx_builder.AppendVertex(vtx); |
| }); |
| } |
| |
| template <typename VertexWriter> |
| void CreateSquareCap(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& offset, |
| Scalar scale, |
| bool reverse) { |
| Point orientation = offset * (reverse ? -1 : 1); |
| Point forward(offset.y, -offset.x); |
| |
| Point vtx = position + orientation; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position - orientation; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position + orientation + forward; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position - orientation + forward; |
| vtx_builder.AppendVertex(vtx); |
| } |
| |
| template <typename VertexWriter> |
| Scalar CreateBevelAndGetDirection(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& start_offset, |
| const Point& end_offset) { |
| Point vtx = position; |
| vtx_builder.AppendVertex(vtx); |
| |
| Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1; |
| vtx = position + start_offset * dir; |
| vtx_builder.AppendVertex(vtx); |
| vtx = position + end_offset * dir; |
| vtx_builder.AppendVertex(vtx); |
| |
| return dir; |
| } |
| |
| template <typename VertexWriter> |
| void CreateMiterJoin(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& start_offset, |
| const Point& end_offset, |
| Scalar miter_limit, |
| Scalar scale) { |
| Point start_normal = start_offset.Normalize(); |
| Point end_normal = end_offset.Normalize(); |
| |
| // 1 for no joint (straight line), 0 for max joint (180 degrees). |
| Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2; |
| if (ScalarNearlyEqual(alignment, 1)) { |
| return; |
| } |
| |
| Scalar direction = CreateBevelAndGetDirection(vtx_builder, position, |
| start_offset, end_offset); |
| |
| Point miter_point = (((start_offset + end_offset) / 2) / alignment); |
| if (miter_point.GetDistanceSquared({0, 0}) > miter_limit * miter_limit) { |
| return; // Convert to bevel when we exceed the miter limit. |
| } |
| |
| // Outer miter point. |
| VS::PerVertexData vtx; |
| vtx.position = position + miter_point * direction; |
| vtx_builder.AppendVertex(vtx.position); |
| } |
| |
| template <typename VertexWriter> |
| void CreateRoundJoin(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& start_offset, |
| const Point& end_offset, |
| Scalar miter_limit, |
| Scalar scale) { |
| Point start_normal = start_offset.Normalize(); |
| Point end_normal = end_offset.Normalize(); |
| |
| // 0 for no joint (straight line), 1 for max joint (180 degrees). |
| Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2; |
| if (ScalarNearlyEqual(alignment, 0)) { |
| return; |
| } |
| |
| Scalar direction = CreateBevelAndGetDirection(vtx_builder, position, |
| start_offset, end_offset); |
| |
| Point middle = |
| (start_offset + end_offset).Normalize() * start_offset.GetLength(); |
| Point middle_normal = middle.Normalize(); |
| |
| Point middle_handle = middle + Point(-middle.y, middle.x) * |
| PathBuilder::kArcApproximationMagic * |
| alignment * direction; |
| Point start_handle = start_offset + Point(start_offset.y, -start_offset.x) * |
| PathBuilder::kArcApproximationMagic * |
| alignment * direction; |
| |
| VS::PerVertexData vtx; |
| CubicPathComponent(start_offset, start_handle, middle_handle, middle) |
| .ToLinearPathComponents(scale, [&vtx_builder, direction, &vtx, position, |
| middle_normal](const Point& point) { |
| vtx.position = position + point * direction; |
| vtx_builder.AppendVertex(vtx.position); |
| vtx.position = position + (-point * direction).Reflect(middle_normal); |
| vtx_builder.AppendVertex(vtx.position); |
| }); |
| } |
| |
| template <typename VertexWriter> |
| void CreateBevelJoin(VertexWriter& vtx_builder, |
| const Point& position, |
| const Point& start_offset, |
| const Point& end_offset, |
| Scalar miter_limit, |
| Scalar scale) { |
| CreateBevelAndGetDirection(vtx_builder, position, start_offset, end_offset); |
| } |
| |
| template <typename VertexWriter> |
| void CreateSolidStrokeVertices(VertexWriter& vtx_builder, |
| const Path::Polyline& polyline, |
| Scalar stroke_width, |
| Scalar scaled_miter_limit, |
| const JoinProc<VertexWriter>& join_proc, |
| const CapProc<VertexWriter>& cap_proc, |
| Scalar scale) { |
| StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit, |
| join_proc, cap_proc, scale); |
| stroke_generator.Generate(vtx_builder); |
| } |
| |
| // static |
| template <typename VertexWriter> |
| JoinProc<VertexWriter> GetJoinProc(Join stroke_join) { |
| switch (stroke_join) { |
| case Join::kBevel: |
| return &CreateBevelJoin<VertexWriter>; |
| case Join::kMiter: |
| return &CreateMiterJoin<VertexWriter>; |
| case Join::kRound: |
| return &CreateRoundJoin<VertexWriter>; |
| } |
| } |
| |
| template <typename VertexWriter> |
| CapProc<VertexWriter> GetCapProc(Cap stroke_cap) { |
| switch (stroke_cap) { |
| case Cap::kButt: |
| return &CreateButtCap<VertexWriter>; |
| case Cap::kRound: |
| return &CreateRoundCap<VertexWriter>; |
| case Cap::kSquare: |
| return &CreateSquareCap<VertexWriter>; |
| } |
| } |
| } // namespace |
| |
| std::vector<SolidFillVertexShader::PerVertexData> |
| StrokePathGeometry::GenerateSolidStrokeVertices(const Path::Polyline& polyline, |
| Scalar stroke_width, |
| Scalar miter_limit, |
| Join stroke_join, |
| Cap stroke_cap, |
| Scalar scale) { |
| auto scaled_miter_limit = stroke_width * miter_limit * 0.5f; |
| auto join_proc = GetJoinProc<PositionWriter>(stroke_join); |
| auto cap_proc = GetCapProc<PositionWriter>(stroke_cap); |
| StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit, |
| join_proc, cap_proc, scale); |
| PositionWriter vtx_builder; |
| stroke_generator.Generate(vtx_builder); |
| return vtx_builder.GetData(); |
| } |
| |
| StrokePathGeometry::StrokePathGeometry(const Path& path, |
| Scalar stroke_width, |
| Scalar miter_limit, |
| Cap stroke_cap, |
| Join stroke_join) |
| : path_(path), |
| stroke_width_(stroke_width), |
| miter_limit_(miter_limit), |
| stroke_cap_(stroke_cap), |
| stroke_join_(stroke_join) {} |
| |
| StrokePathGeometry::~StrokePathGeometry() = default; |
| |
| Scalar StrokePathGeometry::GetStrokeWidth() const { |
| return stroke_width_; |
| } |
| |
| Scalar StrokePathGeometry::GetMiterLimit() const { |
| return miter_limit_; |
| } |
| |
| Cap StrokePathGeometry::GetStrokeCap() const { |
| return stroke_cap_; |
| } |
| |
| Join StrokePathGeometry::GetStrokeJoin() const { |
| return stroke_join_; |
| } |
| |
| Scalar StrokePathGeometry::ComputeAlphaCoverage(const Entity& entity) const { |
| Scalar scaled_stroke_width = |
| entity.GetTransform().GetMaxBasisLengthXY() * stroke_width_; |
| // If the stroke width is 0 or greater than kMinStrokeSizeMSAA, don't apply |
| // any additional alpha. This is intended to match Skia behavior. |
| if (scaled_stroke_width == 0.0 || scaled_stroke_width >= kMinStrokeSizeMSAA) { |
| return 1.0; |
| } |
| // This scalling is eyeballed from Skia. |
| return std::clamp(scaled_stroke_width * 20.0f, 0.f, 1.f); |
| } |
| |
| GeometryResult StrokePathGeometry::GetPositionBuffer( |
| const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| if (stroke_width_ < 0.0) { |
| return {}; |
| } |
| auto determinant = entity.GetTransform().GetDeterminant(); |
| if (determinant == 0) { |
| return {}; |
| } |
| |
| Scalar min_size = |
| (pass.GetSampleCount() == SampleCount::kCount4 ? kMinStrokeSizeMSAA |
| : kMinStrokeSize) / |
| sqrt(std::abs(determinant)); |
| Scalar stroke_width = std::max(stroke_width_, min_size); |
| |
| auto& host_buffer = renderer.GetTransientsBuffer(); |
| auto scale = entity.GetTransform().GetMaxBasisLength(); |
| |
| PositionWriter position_writer; |
| auto polyline = renderer.GetTessellator()->CreateTempPolyline(path_, scale); |
| CreateSolidStrokeVertices(position_writer, polyline, stroke_width, |
| miter_limit_ * stroke_width_ * 0.5f, |
| GetJoinProc<PositionWriter>(stroke_join_), |
| GetCapProc<PositionWriter>(stroke_cap_), scale); |
| |
| BufferView buffer_view = |
| host_buffer.Emplace(position_writer.GetData().data(), |
| position_writer.GetData().size() * |
| sizeof(SolidFillVertexShader::PerVertexData), |
| alignof(SolidFillVertexShader::PerVertexData)); |
| |
| return GeometryResult{ |
| .type = PrimitiveType::kTriangleStrip, |
| .vertex_buffer = |
| { |
| .vertex_buffer = buffer_view, |
| .vertex_count = position_writer.GetData().size(), |
| .index_type = IndexType::kNone, |
| }, |
| .transform = entity.GetShaderTransform(pass), |
| .mode = GeometryResult::Mode::kPreventOverdraw}; |
| } |
| |
| GeometryResult::Mode StrokePathGeometry::GetResultMode() const { |
| return GeometryResult::Mode::kPreventOverdraw; |
| } |
| |
| std::optional<Rect> StrokePathGeometry::GetCoverage( |
| const Matrix& transform) const { |
| auto path_bounds = path_.GetBoundingBox(); |
| if (!path_bounds.has_value()) { |
| return std::nullopt; |
| } |
| |
| Scalar max_radius = 0.5; |
| if (stroke_cap_ == Cap::kSquare) { |
| max_radius = max_radius * kSqrt2; |
| } |
| if (stroke_join_ == Join::kMiter) { |
| max_radius = std::max(max_radius, miter_limit_ * 0.5f); |
| } |
| Scalar determinant = transform.GetDeterminant(); |
| if (determinant == 0) { |
| return std::nullopt; |
| } |
| // Use the most conervative coverage setting. |
| Scalar min_size = kMinStrokeSize / sqrt(std::abs(determinant)); |
| max_radius *= std::max(stroke_width_, min_size); |
| return path_bounds->Expand(max_radius).TransformBounds(transform); |
| } |
| |
| } // namespace impeller |