| // 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() = default; |
| |
| PathBuilder::~PathBuilder() = default; |
| |
| Path PathBuilder::CopyPath(FillType fill) const { |
| auto path = prototype_; |
| path.SetFillType(fill); |
| return path; |
| } |
| |
| Path PathBuilder::TakePath(FillType fill) { |
| auto path = prototype_; |
| path.SetFillType(fill); |
| path.SetConvexity(convexity_); |
| return path; |
| } |
| |
| PathBuilder& PathBuilder::MoveTo(Point point, bool relative) { |
| current_ = relative ? current_ + point : point; |
| subpath_start_ = current_; |
| prototype_.AddContourComponent(current_); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::Close() { |
| LineTo(subpath_start_); |
| prototype_.SetContourClosed(true); |
| prototype_.AddContourComponent(current_); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::LineTo(Point point, bool relative) { |
| point = relative ? current_ + point : point; |
| prototype_.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}; |
| prototype_.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}; |
| prototype_.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; |
| prototype_.AddQuadraticComponent(current_, controlPoint, point); |
| current_ = point; |
| return *this; |
| } |
| |
| Point PathBuilder::ReflectedQuadraticControlPoint1() const { |
| /* |
| * If there is no previous command or if the previous command was not a |
| * quadratic, assume the control point is coincident with the current point. |
| */ |
| if (prototype_.GetComponentCount() == 0) { |
| return current_; |
| } |
| |
| QuadraticPathComponent quad; |
| if (!prototype_.GetQuadraticComponentAtIndex( |
| prototype_.GetComponentCount() - 1, quad)) { |
| return current_; |
| } |
| |
| /* |
| * The control point is assumed to be the reflection of the control point on |
| * the previous command relative to the current point. |
| */ |
| return (current_ * 2.0) - quad.cp; |
| } |
| |
| PathBuilder& PathBuilder::SmoothQuadraticCurveTo(Point point, bool relative) { |
| point = relative ? current_ + point : point; |
| /* |
| * The reflected control point is absolute and we made the endpoint absolute |
| * too. So there the last argument is always false (i.e, not relative). |
| */ |
| QuadraticCurveTo(point, ReflectedQuadraticControlPoint1(), false); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::SetConvexity(Convexity value) { |
| 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; |
| prototype_.AddCubicComponent(current_, controlPoint1, controlPoint2, point); |
| current_ = point; |
| return *this; |
| } |
| |
| Point PathBuilder::ReflectedCubicControlPoint1() const { |
| /* |
| * If there is no previous command or if the previous command was not a |
| * cubic, assume the first control point is coincident with the current |
| * point. |
| */ |
| if (prototype_.GetComponentCount() == 0) { |
| return current_; |
| } |
| |
| CubicPathComponent cubic; |
| if (!prototype_.GetCubicComponentAtIndex(prototype_.GetComponentCount() - 1, |
| cubic)) { |
| return current_; |
| } |
| |
| /* |
| * The first control point is assumed to be the reflection of the second |
| * control point on the previous command relative to the current point. |
| */ |
| return (current_ * 2.0) - cubic.cp2; |
| } |
| |
| PathBuilder& PathBuilder::SmoothCubicCurveTo(Point controlPoint2, |
| Point point, |
| bool relative) { |
| auto controlPoint1 = ReflectedCubicControlPoint1(); |
| controlPoint2 = relative ? current_ + controlPoint2 : controlPoint2; |
| auto endpoint = relative ? current_ + point : point; |
| |
| CubicCurveTo(endpoint, // endpoint |
| controlPoint1, // control point 1 |
| controlPoint2, // control point 2 |
| false // relative since all points are already absolute |
| ); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddQuadraticCurve(Point p1, Point cp, Point p2) { |
| MoveTo(p1); |
| prototype_.AddQuadraticComponent(p1, cp, p2); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddCubicCurve(Point p1, |
| Point cp1, |
| Point cp2, |
| Point p2) { |
| MoveTo(p1); |
| prototype_.AddCubicComponent(p1, cp1, cp2, p2); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRect(Rect rect) { |
| current_ = rect.origin; |
| |
| auto tl = rect.origin; |
| auto bl = rect.origin + Point{0.0, rect.size.height}; |
| auto br = rect.origin + Point{rect.size.width, rect.size.height}; |
| auto tr = rect.origin + Point{rect.size.width, 0.0}; |
| |
| MoveTo(tl); |
| prototype_.AddLinearComponent(tl, tr) |
| .AddLinearComponent(tr, br) |
| .AddLinearComponent(br, bl); |
| Close(); |
| |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddCircle(const Point& c, Scalar r) { |
| return AddOval(Rect{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, {radius, radius, radius, radius}); |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRect(Rect rect, RoundingRadii radii) { |
| if (radii.AreAllZero()) { |
| return AddRect(rect); |
| } |
| |
| current_ = rect.origin + Point{radii.top_left.x, 0.0}; |
| |
| MoveTo({rect.origin.x + radii.top_left.x, rect.origin.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top line. |
| // |
| prototype_.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. |
| // |
| prototype_.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. |
| // |
| prototype_.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. |
| // |
| prototype_.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; |
| prototype_.AddCubicComponent( |
| {rect.origin.x, rect.origin.y + radii.top_left.y}, |
| {rect.origin.x, rect.origin.y + radii.top_left.y - magic_top_left.y}, |
| {rect.origin.x + radii.top_left.x - magic_top_left.x, rect.origin.y}, |
| {rect.origin.x + radii.top_left.x, rect.origin.y}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectTopRight(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_top_right = radii.top_right * kArcApproximationMagic; |
| prototype_.AddCubicComponent( |
| {rect.origin.x + rect.size.width - radii.top_right.x, rect.origin.y}, |
| {rect.origin.x + rect.size.width - radii.top_right.x + magic_top_right.x, |
| rect.origin.y}, |
| {rect.origin.x + rect.size.width, |
| rect.origin.y + radii.top_right.y - magic_top_right.y}, |
| {rect.origin.x + rect.size.width, rect.origin.y + radii.top_right.y}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectBottomRight(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic; |
| prototype_.AddCubicComponent( |
| {rect.origin.x + rect.size.width, |
| rect.origin.y + rect.size.height - radii.bottom_right.y}, |
| {rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - |
| radii.bottom_right.y + |
| magic_bottom_right.y}, |
| {rect.origin.x + rect.size.width - radii.bottom_right.x + |
| magic_bottom_right.x, |
| rect.origin.y + rect.size.height}, |
| {rect.origin.x + rect.size.width - radii.bottom_right.x, |
| rect.origin.y + rect.size.height}); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::AddRoundedRectBottomLeft(Rect rect, |
| RoundingRadii radii) { |
| const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic; |
| prototype_.AddCubicComponent( |
| {rect.origin.x + radii.bottom_left.x, rect.origin.y + rect.size.height}, |
| {rect.origin.x + radii.bottom_left.x - magic_bottom_left.x, |
| rect.origin.y + rect.size.height}, |
| {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y + |
| magic_bottom_left.y}, |
| {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y}); |
| return *this; |
| } |
| |
| 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 radius = {oval_bounds.size.width * 0.5f, |
| oval_bounds.size.height * 0.5f}; |
| const Point center = {oval_bounds.origin.x + radius.x, |
| oval_bounds.origin.y + radius.y}; |
| |
| 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; |
| |
| prototype_.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 r = {container.size.width * 0.5f, container.size.height * 0.5f}; |
| const Point c = {container.origin.x + r.x, container.origin.y + r.y}; |
| const Point m = {kArcApproximationMagic * r.x, kArcApproximationMagic * r.y}; |
| |
| MoveTo({c.x, c.y - r.y}); |
| |
| //---------------------------------------------------------------------------- |
| // Top right arc. |
| // |
| prototype_.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. |
| // |
| prototype_.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. |
| // |
| prototype_.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. |
| // |
| prototype_.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); |
| prototype_.AddLinearComponent(p1, p2); |
| return *this; |
| } |
| |
| const Path& PathBuilder::GetCurrentPath() const { |
| return prototype_; |
| } |
| |
| PathBuilder& PathBuilder::AddPath(const Path& path) { |
| auto linear = [&](size_t index, const LinearPathComponent& l) { |
| prototype_.AddLinearComponent(l.p1, l.p2); |
| }; |
| auto quadratic = [&](size_t index, const QuadraticPathComponent& q) { |
| prototype_.AddQuadraticComponent(q.p1, q.cp, q.p2); |
| }; |
| auto cubic = [&](size_t index, const CubicPathComponent& c) { |
| prototype_.AddCubicComponent(c.p1, c.cp1, c.cp2, c.p2); |
| }; |
| auto move = [&](size_t index, const ContourComponent& m) { |
| prototype_.AddContourComponent(m.destination); |
| }; |
| path.EnumerateComponents(linear, quadratic, cubic, move); |
| return *this; |
| } |
| |
| } // namespace impeller |