1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:27:35 +00:00

LibGfx+LibWeb: Wire up CanvasRenderingContext2D.ellipse()

Note that this is *extremely* naive, and not very good at being correct.
This commit is contained in:
AnotherTest 2021-04-15 18:41:13 +04:30 committed by Andreas Kling
parent 6c05d6d370
commit 801daf47f0
6 changed files with 104 additions and 12 deletions

View file

@ -1492,7 +1492,7 @@ void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_po
static bool can_approximate_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta) static bool can_approximate_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
{ {
constexpr static float tolerance = 0.5f; constexpr static float tolerance = 0.3f;
auto half_theta_delta = theta_delta / 2.0f; auto half_theta_delta = theta_delta / 2.0f;

View file

@ -30,11 +30,14 @@
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibGfx/Painter.h> #include <LibGfx/Painter.h>
#include <LibGfx/Path.h> #include <LibGfx/Path.h>
#include <math.h>
namespace Gfx { namespace Gfx {
void Path::elliptical_arc_to(const FloatPoint& next_point, const FloatPoint& radii, double x_axis_rotation, bool large_arc, bool sweep) void Path::elliptical_arc_to(const FloatPoint& point, const FloatPoint& radii, double x_axis_rotation, bool large_arc, bool sweep)
{ {
auto next_point = point;
double rx = radii.x(); double rx = radii.x();
double ry = radii.y(); double ry = radii.y();
@ -58,6 +61,18 @@ void Path::elliptical_arc_to(const FloatPoint& next_point, const FloatPoint& rad
if (ry < 0) if (ry < 0)
ry *= -1.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.move_by(0.01f, 0.01f);
}
// Find (cx, cy), theta_1, theta_delta // Find (cx, cy), theta_1, theta_delta
// Step 1: Compute (x1', y1') // Step 1: Compute (x1', y1')
auto x_avg = (last_point.x() - next_point.x()) / 2.0f; auto x_avg = (last_point.x() - next_point.x()) / 2.0f;
@ -104,20 +119,18 @@ void Path::elliptical_arc_to(const FloatPoint& next_point, const FloatPoint& rad
auto theta_delta = theta_2 - theta_1; auto theta_delta = theta_2 - theta_1;
if (!sweep && theta_delta > 0.0f) { if (!sweep && theta_delta > 0.0f) {
theta_delta -= M_TAU; theta_delta -= 2 * M_PI;
} else if (sweep && theta_delta < 0) { } else if (sweep && theta_delta < 0) {
theta_delta += M_TAU; theta_delta += 2 * M_PI;
} }
append_segment<EllipticalArcSegment>( elliptical_arc_to(
next_point, next_point,
FloatPoint(cx, cy), { cx, cy },
FloatPoint(rx, ry), { rx, ry },
static_cast<float>(x_axis_rotation), x_axis_rotation,
static_cast<float>(theta_1), theta_1,
static_cast<float>(theta_delta)); theta_delta);
invalidate_split_lines();
} }
void Path::close() void Path::close()

View file

@ -160,6 +160,20 @@ public:
elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep); elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep);
} }
// Note: This does not do any sanity checks!
void elliptical_arc_to(const FloatPoint& endpoint, const FloatPoint& center, const FloatPoint& radii, double x_axis_rotation, double theta, double theta_delta)
{
append_segment<EllipticalArcSegment>(
endpoint,
center,
radii,
x_axis_rotation,
theta,
theta_delta);
invalidate_split_lines();
}
void close(); void close();
void close_all_subpaths(); void close_all_subpaths();

View file

@ -188,6 +188,65 @@ void CanvasRenderingContext2D::quadratic_curve_to(float cx, float cy, float x, f
m_path.quadratic_bezier_curve_to({ cx, cy }, { x, y }); m_path.quadratic_bezier_curve_to({ cx, cy }, { x, y });
} }
void CanvasRenderingContext2D::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
{
ellipse(x, y, radius, radius, 0, start_angle, end_angle, counter_clockwise);
}
void CanvasRenderingContext2D::ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise)
{
if ((!counter_clockwise && (end_angle - start_angle) >= M_TAU)
|| (counter_clockwise && (start_angle - end_angle) >= M_TAU)) {
start_angle = 0;
end_angle = M_TAU;
} else {
start_angle = fmodf(start_angle, M_TAU);
end_angle = fmodf(end_angle, M_TAU);
}
// Then, figure out where the ends of the arc are.
// To do so, we can pretend that the center of this ellipse is at (0, 0),
// and the whole coordinate system is rotated `rotation` radians around the x axis, centered on `center`.
// The sign of the resulting relative positions is just whether our angle is on one of the left quadrants.
auto sin_rotation = sinf(rotation);
auto cos_rotation = cosf(rotation);
auto resolve_point_with_angle = [&](float angle) {
auto tan_relative = tanf(angle);
auto tan2 = tan_relative * tan_relative;
auto ab = radius_x * radius_y;
auto a2 = radius_x * radius_x;
auto b2 = radius_y * radius_y;
auto sqrt = sqrtf(b2 + a2 * tan2);
auto relative_x_position = ab / sqrt;
auto relative_y_position = ab * tan_relative / sqrt;
// Make sure to set the correct sign
float sn = sinf(angle) >= 0 ? 1 : -1;
relative_x_position *= sn;
relative_y_position *= sn;
// Now rotate it (back) around the center point by 'rotation' radians, then move it back to our actual origin.
auto relative_rotated_x_position = relative_x_position * cos_rotation - relative_y_position * sin_rotation;
auto relative_rotated_y_position = relative_x_position * sin_rotation + relative_y_position * cos_rotation;
return Gfx::FloatPoint { relative_rotated_x_position + x, relative_rotated_y_position + y };
};
auto start_point = resolve_point_with_angle(start_angle);
auto end_point = resolve_point_with_angle(end_angle);
m_path.move_to(start_point);
auto delta_theta = end_angle - start_angle;
// FIXME: This is still goofy for some values.
m_path.elliptical_arc_to(end_point, { radius_x, radius_y }, rotation, delta_theta > M_PI, !counter_clockwise);
m_path.close();
}
void CanvasRenderingContext2D::rect(float x, float y, float width, float height) void CanvasRenderingContext2D::rect(float x, float y, float width, float height)
{ {
m_path.move_to({ x, y }); m_path.move_to({ x, y });

View file

@ -74,6 +74,9 @@ public:
void move_to(float x, float y); void move_to(float x, float y);
void line_to(float x, float y); void line_to(float x, float y);
void quadratic_curve_to(float cx, float cy, float x, float y); void quadratic_curve_to(float cx, float cy, float x, float y);
void arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise);
void ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise);
void rect(float x, float y, float width, float height); void rect(float x, float y, float width, float height);
void stroke(); void stroke();

View file

@ -15,6 +15,9 @@ interface CanvasRenderingContext2D {
undefined moveTo(double x, double y); undefined moveTo(double x, double y);
undefined lineTo(double x, double y); undefined lineTo(double x, double y);
undefined quadraticCurveTo(double cpx, double cpy, double x, double y); undefined quadraticCurveTo(double cpx, double cpy, double x, double y);
undefined arc(double x, double y, double radius, double startAngle, double endAngle, optional boolean counterclockwise = false);
undefined ellipse(double x, double y, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, optional boolean counterclockwise = false);
undefined rect(double x, double y, double width, double height); undefined rect(double x, double y, double width, double height);
undefined drawImage(HTMLImageElement image, double dx, double dy); undefined drawImage(HTMLImageElement image, double dx, double dy);