mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 18:47:34 +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:
parent
6c05d6d370
commit
801daf47f0
6 changed files with 104 additions and 12 deletions
|
@ -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)
|
||||
{
|
||||
constexpr static float tolerance = 0.5f;
|
||||
constexpr static float tolerance = 0.3f;
|
||||
|
||||
auto half_theta_delta = theta_delta / 2.0f;
|
||||
|
||||
|
|
|
@ -30,11 +30,14 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <math.h>
|
||||
|
||||
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 ry = radii.y();
|
||||
|
||||
|
@ -58,6 +61,18 @@ void Path::elliptical_arc_to(const FloatPoint& next_point, const FloatPoint& rad
|
|||
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.move_by(0.01f, 0.01f);
|
||||
}
|
||||
|
||||
// Find (cx, cy), theta_1, theta_delta
|
||||
// Step 1: Compute (x1', y1')
|
||||
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;
|
||||
|
||||
if (!sweep && theta_delta > 0.0f) {
|
||||
theta_delta -= M_TAU;
|
||||
theta_delta -= 2 * M_PI;
|
||||
} else if (sweep && theta_delta < 0) {
|
||||
theta_delta += M_TAU;
|
||||
theta_delta += 2 * M_PI;
|
||||
}
|
||||
|
||||
append_segment<EllipticalArcSegment>(
|
||||
elliptical_arc_to(
|
||||
next_point,
|
||||
FloatPoint(cx, cy),
|
||||
FloatPoint(rx, ry),
|
||||
static_cast<float>(x_axis_rotation),
|
||||
static_cast<float>(theta_1),
|
||||
static_cast<float>(theta_delta));
|
||||
|
||||
invalidate_split_lines();
|
||||
{ cx, cy },
|
||||
{ rx, ry },
|
||||
x_axis_rotation,
|
||||
theta_1,
|
||||
theta_delta);
|
||||
}
|
||||
|
||||
void Path::close()
|
||||
|
|
|
@ -160,6 +160,20 @@ public:
|
|||
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_all_subpaths();
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
m_path.move_to({ x, y });
|
||||
|
|
|
@ -74,6 +74,9 @@ public:
|
|||
void move_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 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 stroke();
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ interface CanvasRenderingContext2D {
|
|||
undefined moveTo(double x, double y);
|
||||
undefined lineTo(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 drawImage(HTMLImageElement image, double dx, double dy);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue