mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 13:55:06 +00:00

Remove SplitLineSegment and replace it with a FloatLine, nobody was interested in its extra fields anymore. Also, remove the sorting of the split segments, this really should not have been done here anyway, and is not required by the rasterizer anymore. Keeping the segments in stroke order will also make it possible to generate stroked path geometry (in future).
368 lines
12 KiB
C++
368 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Function.h>
|
|
#include <AK/HashTable.h>
|
|
#include <AK/Math.h>
|
|
#include <AK/QuickSort.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibGfx/Painter.h>
|
|
#include <LibGfx/Path.h>
|
|
|
|
namespace Gfx {
|
|
|
|
void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, double x_axis_rotation, bool large_arc, bool sweep)
|
|
{
|
|
auto next_point = point;
|
|
|
|
double rx = radii.width();
|
|
double ry = radii.height();
|
|
|
|
double x_axis_rotation_c = AK::cos(x_axis_rotation);
|
|
double x_axis_rotation_s = AK::sin(x_axis_rotation);
|
|
|
|
// Find the last point
|
|
FloatPoint last_point { 0, 0 };
|
|
if (!m_segments.is_empty())
|
|
last_point = m_segments.last()->point();
|
|
|
|
// Step 1 of out-of-range radii correction
|
|
if (rx == 0.0 || ry == 0.0) {
|
|
append_segment<LineSegment>(next_point);
|
|
return;
|
|
}
|
|
|
|
// Step 2 of out-of-range radii correction
|
|
if (rx < 0)
|
|
rx *= -1.0;
|
|
if (ry < 0)
|
|
ry *= -1.0;
|
|
|
|
// POSSIBLY HACK: Handle the case where both points are the same.
|
|
auto same_endpoints = next_point == last_point;
|
|
if (same_endpoints) {
|
|
if (!large_arc) {
|
|
// Nothing is going to be drawn anyway.
|
|
return;
|
|
}
|
|
|
|
// Move the endpoint by a small amount to avoid division by zero.
|
|
next_point.translate_by(0.01f, 0.01f);
|
|
}
|
|
|
|
// Find (cx, cy), theta_1, theta_delta
|
|
// Step 1: Compute (x1', y1')
|
|
auto x_avg = static_cast<double>(last_point.x() - next_point.x()) / 2.0;
|
|
auto y_avg = static_cast<double>(last_point.y() - next_point.y()) / 2.0;
|
|
auto x1p = x_axis_rotation_c * x_avg + x_axis_rotation_s * y_avg;
|
|
auto y1p = -x_axis_rotation_s * x_avg + x_axis_rotation_c * y_avg;
|
|
|
|
// Step 2: Compute (cx', cy')
|
|
double x1p_sq = x1p * x1p;
|
|
double y1p_sq = y1p * y1p;
|
|
double rx_sq = rx * rx;
|
|
double ry_sq = ry * ry;
|
|
|
|
// Step 3 of out-of-range radii correction
|
|
double lambda = x1p_sq / rx_sq + y1p_sq / ry_sq;
|
|
double multiplier;
|
|
|
|
if (lambda > 1.0) {
|
|
auto lambda_sqrt = AK::sqrt(lambda);
|
|
rx *= lambda_sqrt;
|
|
ry *= lambda_sqrt;
|
|
multiplier = 0.0;
|
|
} else {
|
|
double numerator = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq;
|
|
double denominator = rx_sq * y1p_sq + ry_sq * x1p_sq;
|
|
multiplier = AK::sqrt(numerator / denominator);
|
|
}
|
|
|
|
if (large_arc == sweep)
|
|
multiplier *= -1.0;
|
|
|
|
double cxp = multiplier * rx * y1p / ry;
|
|
double cyp = multiplier * -ry * x1p / rx;
|
|
|
|
// Step 3: Compute (cx, cy) from (cx', cy')
|
|
x_avg = (last_point.x() + next_point.x()) / 2.0f;
|
|
y_avg = (last_point.y() + next_point.y()) / 2.0f;
|
|
double cx = x_axis_rotation_c * cxp - x_axis_rotation_s * cyp + x_avg;
|
|
double cy = x_axis_rotation_s * cxp + x_axis_rotation_c * cyp + y_avg;
|
|
|
|
double theta_1 = AK::atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
|
|
double theta_2 = AK::atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);
|
|
|
|
auto theta_delta = theta_2 - theta_1;
|
|
|
|
if (!sweep && theta_delta > 0.0) {
|
|
theta_delta -= 2 * M_PI;
|
|
} else if (sweep && theta_delta < 0) {
|
|
theta_delta += 2 * M_PI;
|
|
}
|
|
|
|
elliptical_arc_to(
|
|
next_point,
|
|
{ cx, cy },
|
|
{ rx, ry },
|
|
x_axis_rotation,
|
|
theta_1,
|
|
theta_delta,
|
|
large_arc,
|
|
sweep);
|
|
}
|
|
|
|
void Path::close()
|
|
{
|
|
if (m_segments.size() <= 1)
|
|
return;
|
|
|
|
auto last_point = m_segments.last()->point();
|
|
|
|
for (ssize_t i = m_segments.size() - 1; i >= 0; --i) {
|
|
auto& segment = m_segments[i];
|
|
if (segment->type() == Segment::Type::MoveTo) {
|
|
if (last_point == segment->point())
|
|
return;
|
|
append_segment<LineSegment>(segment->point());
|
|
invalidate_split_lines();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Path::close_all_subpaths()
|
|
{
|
|
if (m_segments.size() <= 1)
|
|
return;
|
|
|
|
invalidate_split_lines();
|
|
|
|
Optional<FloatPoint> cursor, start_of_subpath;
|
|
bool is_first_point_in_subpath { false };
|
|
|
|
auto segment_count = m_segments.size();
|
|
for (size_t i = 0; i < segment_count; i++) {
|
|
// Note: We need to use m_segments[i] as append_segment() may invalidate any references.
|
|
switch (m_segments[i]->type()) {
|
|
case Segment::Type::MoveTo: {
|
|
if (cursor.has_value() && !is_first_point_in_subpath) {
|
|
// This is a move from a subpath to another
|
|
// connect the two ends of this subpath before
|
|
// moving on to the next one
|
|
VERIFY(start_of_subpath.has_value());
|
|
|
|
append_segment<MoveSegment>(cursor.value());
|
|
append_segment<LineSegment>(start_of_subpath.value());
|
|
}
|
|
is_first_point_in_subpath = true;
|
|
cursor = m_segments[i]->point();
|
|
break;
|
|
}
|
|
case Segment::Type::LineTo:
|
|
case Segment::Type::QuadraticBezierCurveTo:
|
|
case Segment::Type::CubicBezierCurveTo:
|
|
case Segment::Type::EllipticalArcTo:
|
|
if (is_first_point_in_subpath) {
|
|
start_of_subpath = cursor;
|
|
is_first_point_in_subpath = false;
|
|
}
|
|
cursor = m_segments[i]->point();
|
|
break;
|
|
case Segment::Type::Invalid:
|
|
VERIFY_NOT_REACHED();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DeprecatedString Path::to_deprecated_string() const
|
|
{
|
|
StringBuilder builder;
|
|
builder.append("Path { "sv);
|
|
for (auto& segment : m_segments) {
|
|
switch (segment->type()) {
|
|
case Segment::Type::MoveTo:
|
|
builder.append("MoveTo"sv);
|
|
break;
|
|
case Segment::Type::LineTo:
|
|
builder.append("LineTo"sv);
|
|
break;
|
|
case Segment::Type::QuadraticBezierCurveTo:
|
|
builder.append("QuadraticBezierCurveTo"sv);
|
|
break;
|
|
case Segment::Type::CubicBezierCurveTo:
|
|
builder.append("CubicBezierCurveTo"sv);
|
|
break;
|
|
case Segment::Type::EllipticalArcTo:
|
|
builder.append("EllipticalArcTo"sv);
|
|
break;
|
|
case Segment::Type::Invalid:
|
|
builder.append("Invalid"sv);
|
|
break;
|
|
}
|
|
builder.appendff("({}", segment->point());
|
|
|
|
switch (segment->type()) {
|
|
case Segment::Type::QuadraticBezierCurveTo:
|
|
builder.append(", "sv);
|
|
builder.append(static_cast<QuadraticBezierCurveSegment const&>(*segment).through().to_deprecated_string());
|
|
break;
|
|
case Segment::Type::CubicBezierCurveTo:
|
|
builder.append(", "sv);
|
|
builder.append(static_cast<CubicBezierCurveSegment const&>(*segment).through_0().to_deprecated_string());
|
|
builder.append(", "sv);
|
|
builder.append(static_cast<CubicBezierCurveSegment const&>(*segment).through_1().to_deprecated_string());
|
|
break;
|
|
case Segment::Type::EllipticalArcTo: {
|
|
auto& arc = static_cast<EllipticalArcSegment const&>(*segment);
|
|
builder.appendff(", {}, {}, {}, {}, {}",
|
|
arc.radii().to_deprecated_string().characters(),
|
|
arc.center().to_deprecated_string().characters(),
|
|
arc.x_axis_rotation(),
|
|
arc.theta_1(),
|
|
arc.theta_delta());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
builder.append(") "sv);
|
|
}
|
|
builder.append('}');
|
|
return builder.to_deprecated_string();
|
|
}
|
|
|
|
void Path::segmentize_path()
|
|
{
|
|
Vector<FloatLine> segments;
|
|
float min_x = 0;
|
|
float min_y = 0;
|
|
float max_x = 0;
|
|
float max_y = 0;
|
|
|
|
bool first = true;
|
|
auto add_point_to_bbox = [&](Gfx::FloatPoint point) {
|
|
float x = point.x();
|
|
float y = point.y();
|
|
if (first) {
|
|
min_x = max_x = x;
|
|
min_y = max_y = y;
|
|
first = false;
|
|
} else {
|
|
min_x = min(min_x, x);
|
|
min_y = min(min_y, y);
|
|
max_x = max(max_x, x);
|
|
max_y = max(max_y, y);
|
|
}
|
|
};
|
|
|
|
auto add_line = [&](auto const& p0, auto const& p1) {
|
|
segments.append({ p0, p1 });
|
|
add_point_to_bbox(p1);
|
|
};
|
|
|
|
FloatPoint cursor { 0, 0 };
|
|
for (auto& segment : m_segments) {
|
|
switch (segment->type()) {
|
|
case Segment::Type::MoveTo:
|
|
add_point_to_bbox(segment->point());
|
|
cursor = segment->point();
|
|
break;
|
|
case Segment::Type::LineTo: {
|
|
add_line(cursor, segment->point());
|
|
cursor = segment->point();
|
|
break;
|
|
}
|
|
case Segment::Type::QuadraticBezierCurveTo: {
|
|
auto control = static_cast<QuadraticBezierCurveSegment const&>(*segment).through();
|
|
Painter::for_each_line_segment_on_bezier_curve(control, cursor, segment->point(), [&](FloatPoint p0, FloatPoint p1) {
|
|
add_line(p0, p1);
|
|
});
|
|
cursor = segment->point();
|
|
break;
|
|
}
|
|
case Segment::Type::CubicBezierCurveTo: {
|
|
auto& curve = static_cast<CubicBezierCurveSegment const&>(*segment);
|
|
auto control_0 = curve.through_0();
|
|
auto control_1 = curve.through_1();
|
|
Painter::for_each_line_segment_on_cubic_bezier_curve(control_0, control_1, cursor, segment->point(), [&](FloatPoint p0, FloatPoint p1) {
|
|
add_line(p0, p1);
|
|
});
|
|
cursor = segment->point();
|
|
break;
|
|
}
|
|
case Segment::Type::EllipticalArcTo: {
|
|
auto& arc = static_cast<EllipticalArcSegment const&>(*segment);
|
|
Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](FloatPoint p0, FloatPoint p1) {
|
|
add_line(p0, p1);
|
|
});
|
|
cursor = segment->point();
|
|
break;
|
|
}
|
|
case Segment::Type::Invalid:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
first = false;
|
|
}
|
|
|
|
m_split_lines = move(segments);
|
|
m_bounding_box = Gfx::FloatRect { min_x, min_y, max_x - min_x, max_y - min_y };
|
|
}
|
|
|
|
Path Path::copy_transformed(Gfx::AffineTransform const& transform) const
|
|
{
|
|
Path result;
|
|
|
|
for (auto const& segment : m_segments) {
|
|
switch (segment->type()) {
|
|
case Segment::Type::MoveTo:
|
|
result.move_to(transform.map(segment->point()));
|
|
break;
|
|
case Segment::Type::LineTo: {
|
|
result.line_to(transform.map(segment->point()));
|
|
break;
|
|
}
|
|
case Segment::Type::QuadraticBezierCurveTo: {
|
|
auto const& quadratic_segment = static_cast<QuadraticBezierCurveSegment const&>(*segment);
|
|
result.quadratic_bezier_curve_to(transform.map(quadratic_segment.through()), transform.map(segment->point()));
|
|
break;
|
|
}
|
|
case Segment::Type::CubicBezierCurveTo: {
|
|
auto const& cubic_segment = static_cast<CubicBezierCurveSegment const&>(*segment);
|
|
result.cubic_bezier_curve_to(transform.map(cubic_segment.through_0()), transform.map(cubic_segment.through_1()), transform.map(segment->point()));
|
|
break;
|
|
}
|
|
case Segment::Type::EllipticalArcTo: {
|
|
auto const& arc_segment = static_cast<EllipticalArcSegment const&>(*segment);
|
|
result.elliptical_arc_to(
|
|
transform.map(segment->point()),
|
|
transform.map(arc_segment.center()),
|
|
transform.map(arc_segment.radii()),
|
|
arc_segment.x_axis_rotation() + transform.rotation(),
|
|
arc_segment.theta_1(),
|
|
arc_segment.theta_delta(),
|
|
arc_segment.large_arc(),
|
|
arc_segment.sweep());
|
|
break;
|
|
}
|
|
case Segment::Type::Invalid:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Path::add_path(Path const& other)
|
|
{
|
|
m_segments.extend(other.m_segments);
|
|
invalidate_split_lines();
|
|
}
|
|
|
|
}
|