| // 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 "path_builder.h" |
| |
| #include <cmath> |
| |
| namespace impeller { |
| |
| PathBuilder::PathBuilder() { |
| AddContourComponent({}); |
| } |
| |
| PathBuilder::~PathBuilder() = default; |
| |
| Path PathBuilder::CopyPath(FillType fill) { |
| prototype_.fill = fill; |
| return Path(prototype_); |
| } |
| |
| Path PathBuilder::TakePath(FillType fill) { |
| prototype_.fill = fill; |
| UpdateBounds(); |
| return Path(prototype_); |
| } |
| |
| void PathBuilder::Reserve(size_t point_size, size_t verb_size) { |
| prototype_.points.reserve(point_size); |
| prototype_.components.reserve(verb_size); |
| } |
| |
| PathBuilder& PathBuilder::MoveTo(Point point, bool relative) { |
| current_ = relative ? current_ + point : point; |
| subpath_start_ = current_; |
| AddContourComponent(current_); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::Close() { |
| LineTo(subpath_start_); |
| SetContourClosed(true); |
| AddContourComponent(current_); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::LineTo(Point point, bool relative) { |
| point = relative ? current_ + point : point; |
| AddLinearComponent(current_, point); |
| current_ = point; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::HorizontalLineTo(Scalar x, bool relative) { |
| Point endpoint = |
| relative ? Point{current_.x + x, current_.y} : Point{x, current_.y}; |
| AddLinearComponent(current_, endpoint); |
| current_ = endpoint; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::VerticalLineTo(Scalar y, bool relative) { |
| Point endpoint = |
| relative ? Point{current_.x, current_.y + y} : Point{current_.x, y}; |
| AddLinearComponent(current_, endpoint); |
| current_ = endpoint; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::QuadraticCurveTo(Point controlPoint, |
| Point point, |
| bool relative) { |
| point = relative ? current_ + point : point; |
| controlPoint = relative ? current_ + controlPoint : controlPoint; |
| AddQuadraticComponent(current_, controlPoint, point); |
| current_ = point; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::SetConvexity(Convexity value) { |
| prototype_.convexity = value; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1, |
| Point controlPoint2, |
| Point point, |
| bool relative) { |
| controlPoint1 = relative ? current_ + controlPoint1 : controlPoint1; |
| controlPoint2 = relative ? current_ + controlPoint2 : controlPoint2; |
| point = relative ? current_ + point : point; |
| AddCubicComponent(current_, controlPoint1, controlPoint2, point); |
| current_ = point; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddQuadraticCurve(Point p1, Point cp, Point p2) { |
| MoveTo(p1); |
| AddQuadraticComponent(p1, cp, p2); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddCubicCurve(Point p1, |
| Point cp1, |
| Point cp2, |
| Point p2) { |
| MoveTo(p1); |
| AddCubicComponent(p1, cp1, cp2, p2); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRect(Rect rect) { |
| auto origin = rect.GetOrigin(); |
| auto size = rect.GetSize(); |
| |
| auto tl = origin; |
| auto bl = origin + Point{0.0, size.height}; |
| auto br = origin + size; |
| auto tr = origin + Point{size.width, 0.0}; |
| |
| MoveTo(tl); |
| LineTo(tr); |
| LineTo(br); |
| LineTo(bl); |
| Close(); |
| |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddCircle(const Point& c, Scalar r) { |
| return AddOval(Rect::MakeXYWH(c.x - r, c.y - r, 2.0f * r, 2.0f * r)); |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Scalar radius) { |
| return radius <= 0.0 ? AddRect(rect) |
| : AddRoundedRect(rect, RoundingRadii(radius)); |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Size radii) { |
| return radii.width <= 0 || radii.height <= 0 |
| ? AddRect(rect) |
| : AddRoundedRect(rect, RoundingRadii(radii)); |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRect(Rect rect, RoundingRadii radii) { |
| if (radii.AreAllZero()) { |
| return AddRect(rect); |
| } |
| |
| auto rect_origin = rect.GetOrigin(); |
| auto rect_size = rect.GetSize(); |
| |
| current_ = rect_origin + Point{radii.top_left.x, 0.0}; |
| |
| MoveTo({rect_origin.x + radii.top_left.x, rect_origin.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top line. |
| // |
| AddLinearComponent( |
| {rect_origin.x + radii.top_left.x, rect_origin.y}, |
| {rect_origin.x + rect_size.width - radii.top_right.x, rect_origin.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top right arc. |
| // |
| AddRoundedRectTopRight(rect, radii); |
| |
| //---------------------------------------------------------------------------- |
| // Right line. |
| // |
| AddLinearComponent( |
| {rect_origin.x + rect_size.width, rect_origin.y + radii.top_right.y}, |
| {rect_origin.x + rect_size.width, |
| rect_origin.y + rect_size.height - radii.bottom_right.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Bottom right arc. |
| // |
| AddRoundedRectBottomRight(rect, radii); |
| |
| //---------------------------------------------------------------------------- |
| // Bottom line. |
| // |
| AddLinearComponent( |
| {rect_origin.x + rect_size.width - radii.bottom_right.x, |
| rect_origin.y + rect_size.height}, |
| {rect_origin.x + radii.bottom_left.x, rect_origin.y + rect_size.height}); |
| |
| //---------------------------------------------------------------------------- |
| // Bottom left arc. |
| // |
| AddRoundedRectBottomLeft(rect, radii); |
| |
| //---------------------------------------------------------------------------- |
| // Left line. |
| // |
| AddLinearComponent( |
| {rect_origin.x, rect_origin.y + rect_size.height - radii.bottom_left.y}, |
| {rect_origin.x, rect_origin.y + radii.top_left.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top left arc. |
| // |
| AddRoundedRectTopLeft(rect, radii); |
| |
| Close(); |
| |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectTopLeft(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_top_left = radii.top_left * kArcApproximationMagic; |
| const auto corner = rect.GetOrigin(); |
| AddCubicComponent({corner.x, corner.y + radii.top_left.y}, |
| {corner.x, corner.y + radii.top_left.y - magic_top_left.y}, |
| {corner.x + radii.top_left.x - magic_top_left.x, corner.y}, |
| {corner.x + radii.top_left.x, corner.y}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectTopRight(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_top_right = radii.top_right * kArcApproximationMagic; |
| const auto corner = rect.GetOrigin() + Point{rect.GetWidth(), 0}; |
| AddCubicComponent( |
| {corner.x - radii.top_right.x, corner.y}, |
| {corner.x - radii.top_right.x + magic_top_right.x, corner.y}, |
| {corner.x, corner.y + radii.top_right.y - magic_top_right.y}, |
| {corner.x, corner.y + radii.top_right.y}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectBottomRight(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic; |
| const auto corner = rect.GetOrigin() + rect.GetSize(); |
| AddCubicComponent( |
| {corner.x, corner.y - radii.bottom_right.y}, |
| {corner.x, corner.y - radii.bottom_right.y + magic_bottom_right.y}, |
| {corner.x - radii.bottom_right.x + magic_bottom_right.x, corner.y}, |
| {corner.x - radii.bottom_right.x, corner.y}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectBottomLeft(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic; |
| const auto corner = rect.GetOrigin() + Point{0, rect.GetHeight()}; |
| AddCubicComponent( |
| {corner.x + radii.bottom_left.x, corner.y}, |
| {corner.x + radii.bottom_left.x - magic_bottom_left.x, corner.y}, |
| {corner.x, corner.y - radii.bottom_left.y + magic_bottom_left.y}, |
| {corner.x, corner.y - radii.bottom_left.y}); |
| return *this; |
| } |
| |
| void PathBuilder::AddContourComponent(const Point& destination, |
| bool is_closed) { |
| auto& components = prototype_.components; |
| auto& contours = prototype_.contours; |
| if (components.size() > 0 && |
| components.back().type == Path::ComponentType::kContour) { |
| // Never insert contiguous contours. |
| contours.back() = ContourComponent(destination, is_closed); |
| } else { |
| contours.emplace_back(ContourComponent(destination, is_closed)); |
| components.emplace_back(Path::ComponentType::kContour, contours.size() - 1); |
| } |
| prototype_.bounds.reset(); |
| } |
| |
| void PathBuilder::AddLinearComponent(const Point& p1, const Point& p2) { |
| auto& points = prototype_.points; |
| auto index = points.size(); |
| points.emplace_back(p1); |
| points.emplace_back(p2); |
| prototype_.components.emplace_back(Path::ComponentType::kLinear, index); |
| prototype_.bounds.reset(); |
| } |
| |
| void PathBuilder::AddQuadraticComponent(const Point& p1, |
| const Point& cp, |
| const Point& p2) { |
| auto& points = prototype_.points; |
| auto index = points.size(); |
| points.emplace_back(p1); |
| points.emplace_back(cp); |
| points.emplace_back(p2); |
| prototype_.components.emplace_back(Path::ComponentType::kQuadratic, index); |
| prototype_.bounds.reset(); |
| } |
| |
| void PathBuilder::AddCubicComponent(const Point& p1, |
| const Point& cp1, |
| const Point& cp2, |
| const Point& p2) { |
| auto& points = prototype_.points; |
| auto index = points.size(); |
| points.emplace_back(p1); |
| points.emplace_back(cp1); |
| points.emplace_back(cp2); |
| points.emplace_back(p2); |
| prototype_.components.emplace_back(Path::ComponentType::kCubic, index); |
| prototype_.bounds.reset(); |
| } |
| |
| void PathBuilder::SetContourClosed(bool is_closed) { |
| prototype_.contours.back().is_closed = is_closed; |
| } |
| |
| PathBuilder& PathBuilder::AddArc(const Rect& oval_bounds, |
| Radians start, |
| Radians sweep, |
| bool use_center) { |
| if (sweep.radians < 0) { |
| start.radians += sweep.radians; |
| sweep.radians *= -1; |
| } |
| sweep.radians = std::min(k2Pi, sweep.radians); |
| start.radians = std::fmod(start.radians, k2Pi); |
| |
| const Point center = oval_bounds.GetCenter(); |
| const Point radius = center - oval_bounds.GetOrigin(); |
| |
| Vector2 p1_unit(std::cos(start.radians), std::sin(start.radians)); |
| |
| if (use_center) { |
| MoveTo(center); |
| LineTo(center + p1_unit * radius); |
| } else { |
| MoveTo(center + p1_unit * radius); |
| } |
| |
| while (sweep.radians > 0) { |
| Vector2 p2_unit; |
| Scalar quadrant_angle; |
| if (sweep.radians < kPiOver2) { |
| quadrant_angle = sweep.radians; |
| p2_unit = Vector2(std::cos(start.radians + quadrant_angle), |
| std::sin(start.radians + quadrant_angle)); |
| } else { |
| quadrant_angle = kPiOver2; |
| p2_unit = Vector2(-p1_unit.y, p1_unit.x); |
| } |
| |
| Vector2 arc_cp_lengths = |
| (quadrant_angle / kPiOver2) * kArcApproximationMagic * radius; |
| |
| Point p1 = center + p1_unit * radius; |
| Point p2 = center + p2_unit * radius; |
| Point cp1 = p1 + Vector2(-p1_unit.y, p1_unit.x) * arc_cp_lengths; |
| Point cp2 = p2 + Vector2(p2_unit.y, -p2_unit.x) * arc_cp_lengths; |
| |
| AddCubicComponent(p1, cp1, cp2, p2); |
| current_ = p2; |
| |
| start.radians += quadrant_angle; |
| sweep.radians -= quadrant_angle; |
| p1_unit = p2_unit; |
| } |
| |
| if (use_center) { |
| Close(); |
| } |
| |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddOval(const Rect& container) { |
| const Point c = container.GetCenter(); |
| const Point r = c - container.GetOrigin(); |
| const Point m = r * kArcApproximationMagic; |
| |
| MoveTo({c.x, c.y - r.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top right arc. |
| // |
| AddCubicComponent({c.x, c.y - r.y}, // p1 |
| {c.x + m.x, c.y - r.y}, // cp1 |
| {c.x + r.x, c.y - m.y}, // cp2 |
| {c.x + r.x, c.y} // p2 |
| ); |
| |
| //---------------------------------------------------------------------------- |
| // Bottom right arc. |
| // |
| AddCubicComponent({c.x + r.x, c.y}, // p1 |
| {c.x + r.x, c.y + m.y}, // cp1 |
| {c.x + m.x, c.y + r.y}, // cp2 |
| {c.x, c.y + r.y} // p2 |
| ); |
| |
| //---------------------------------------------------------------------------- |
| // Bottom left arc. |
| // |
| AddCubicComponent({c.x, c.y + r.y}, // p1 |
| {c.x - m.x, c.y + r.y}, // cp1 |
| {c.x - r.x, c.y + m.y}, // cp2 |
| {c.x - r.x, c.y} // p2 |
| ); |
| |
| //---------------------------------------------------------------------------- |
| // Top left arc. |
| // |
| AddCubicComponent({c.x - r.x, c.y}, // p1 |
| {c.x - r.x, c.y - m.y}, // cp1 |
| {c.x - m.x, c.y - r.y}, // cp2 |
| {c.x, c.y - r.y} // p2 |
| ); |
| |
| Close(); |
| |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddLine(const Point& p1, const Point& p2) { |
| MoveTo(p1); |
| AddLinearComponent(p1, p2); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddPath(const Path& path) { |
| auto linear = [&](size_t index, const LinearPathComponent& l) { |
| AddLinearComponent(l.p1, l.p2); |
| }; |
| auto quadratic = [&](size_t index, const QuadraticPathComponent& q) { |
| AddQuadraticComponent(q.p1, q.cp, q.p2); |
| }; |
| auto cubic = [&](size_t index, const CubicPathComponent& c) { |
| AddCubicComponent(c.p1, c.cp1, c.cp2, c.p2); |
| }; |
| auto move = [&](size_t index, const ContourComponent& m) { |
| AddContourComponent(m.destination); |
| }; |
| path.EnumerateComponents(linear, quadratic, cubic, move); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::Shift(Point offset) { |
| for (auto& point : prototype_.points) { |
| point += offset; |
| } |
| for (auto& contour : prototype_.contours) { |
| contour.destination += offset; |
| } |
| prototype_.bounds.reset(); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::SetBounds(Rect bounds) { |
| prototype_.bounds = bounds; |
| return *this; |
| } |
| |
| void PathBuilder::UpdateBounds() { |
| if (!prototype_.bounds.has_value()) { |
| auto min_max = GetMinMaxCoveragePoints(); |
| if (!min_max.has_value()) { |
| prototype_.bounds.reset(); |
| return; |
| } |
| auto min = min_max->first; |
| auto max = min_max->second; |
| const auto difference = max - min; |
| prototype_.bounds = |
| Rect::MakeXYWH(min.x, min.y, difference.x, difference.y); |
| } |
| } |
| |
| std::optional<std::pair<Point, Point>> PathBuilder::GetMinMaxCoveragePoints() |
| const { |
| auto& points = prototype_.points; |
| |
| if (points.empty()) { |
| return std::nullopt; |
| } |
| |
| std::optional<Point> min, max; |
| |
| auto clamp = [&min, &max](const Point& point) { |
| if (min.has_value()) { |
| min = min->Min(point); |
| } else { |
| min = point; |
| } |
| |
| if (max.has_value()) { |
| max = max->Max(point); |
| } else { |
| max = point; |
| } |
| }; |
| |
| for (const auto& component : prototype_.components) { |
| switch (component.type) { |
| case Path::ComponentType::kLinear: { |
| auto* linear = reinterpret_cast<const LinearPathComponent*>( |
| &points[component.index]); |
| clamp(linear->p1); |
| clamp(linear->p2); |
| break; |
| } |
| case Path::ComponentType::kQuadratic: |
| for (const auto& extrema : |
| reinterpret_cast<const QuadraticPathComponent*>( |
| &points[component.index]) |
| ->Extrema()) { |
| clamp(extrema); |
| } |
| break; |
| case Path::ComponentType::kCubic: |
| for (const auto& extrema : reinterpret_cast<const CubicPathComponent*>( |
| &points[component.index]) |
| ->Extrema()) { |
| clamp(extrema); |
| } |
| break; |
| case Path::ComponentType::kContour: |
| break; |
| } |
| } |
| |
| if (!min.has_value() || !max.has_value()) { |
| return std::nullopt; |
| } |
| |
| return std::make_pair(min.value(), max.value()); |
| } |
| |
| } // namespace impeller |