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:
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)
|
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;
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue