1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 21:37:35 +00:00

LibGfx: Implement PaintStyle for SVG linear gradients

This commit is contained in:
MacDue 2023-04-22 18:31:00 +01:00 committed by Andreas Kling
parent 71938550fa
commit 89d3c6d718
2 changed files with 96 additions and 14 deletions

View file

@ -297,6 +297,23 @@ static auto make_sample_non_relative(IntPoint draw_location, auto sample)
return [=, sample = move(sample)](IntPoint point) { return sample(point.translated(draw_location)); };
}
static auto make_linear_gradient_between_two_points(FloatPoint p0, FloatPoint p1, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length)
{
auto delta = p1 - p0;
auto angle = AK::atan2(delta.y(), delta.x());
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
int gradient_length = ceilf(p1.distance_from(p0));
auto rotated_start_point_x = p0.x() * cos_angle - p0.y() * -sin_angle;
return Gradient {
GradientLine(gradient_length, color_stops, repeat_length, UsePremultipliedAlpha::No),
[=](int x, int y) {
return (x * cos_angle - y * -sin_angle) - rotated_start_point_x;
}
};
}
void CanvasLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const
{
// If x0 = x1 and y0 = y1, then the linear gradient must paint nothing.
@ -307,23 +324,43 @@ void CanvasLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintF
if (color_stops().size() < 2)
return paint([this](IntPoint) { return color_stops().first().color; });
auto delta = m_p1 - m_p0;
auto angle = AK::atan2(delta.y(), delta.x());
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
int gradient_length = ceilf(m_p1.distance_from(m_p0));
auto rotated_start_point_x = m_p0.x() * cos_angle - m_p0.y() * -sin_angle;
Gradient linear_gradient {
GradientLine(gradient_length, color_stops(), repeat_length(), UsePremultipliedAlpha::No),
[=](int x, int y) {
return (x * cos_angle - y * -sin_angle) - rotated_start_point_x;
}
};
auto linear_gradient = make_linear_gradient_between_two_points(m_p0, m_p1, color_stops(), repeat_length());
paint(make_sample_non_relative(physical_bounding_box.location(), linear_gradient.sample_function()));
}
void SVGLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const
{
if (color_stops().is_empty())
return;
// If x1 = x2 and y1 = y2, then the area to be painted will be painted as
// a single color using the color and opacity of the last gradient stop.
if (m_p0 == m_p1)
return paint([this](IntPoint) { return color_stops().last().color; });
if (color_stops().size() < 2)
return paint([this](IntPoint) { return color_stops().first().color; });
// Note: The scaling is removed so enough points on the gradient line are generated.
// Otherwise, if you scale a tiny path the gradient looks pixelated.
FloatPoint scale { 1, 1 };
auto sample_transform = gradient_transform().map([&](auto& transform) {
if (auto inverse = transform.inverse(); inverse.has_value()) {
scale = transform.scale();
return Gfx::AffineTransform {}.scale(scale).multiply(*inverse);
}
return Gfx::AffineTransform {};
});
auto linear_gradient = make_linear_gradient_between_two_points(m_p0.scaled(scale), m_p1.scaled(scale), color_stops(), repeat_length());
paint([&, sampler = linear_gradient.sample_function()](auto point) {
point.translate_by(physical_bounding_box.location());
if (sample_transform.has_value())
point = sample_transform->map(point);
return sampler(point);
});
}
void CanvasConicGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunction paint) const
{
if (color_stops().is_empty())

View file

@ -243,4 +243,49 @@ private:
float m_end_radius { 0.0f };
};
// The following paint styles implement the gradients required for SVGs
class SVGGradientPaintStyle : public GradientPaintStyle {
public:
void set_gradient_transform(Gfx::AffineTransform transform)
{
m_gradient_transform = transform;
}
Optional<Gfx::AffineTransform> const& gradient_transform() const { return m_gradient_transform; }
private:
Optional<Gfx::AffineTransform> m_gradient_transform {};
};
class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle {
public:
static ErrorOr<NonnullRefPtr<SVGLinearGradientPaintStyle>> create(FloatPoint p0, FloatPoint p1)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) SVGLinearGradientPaintStyle(p0, p1));
}
void set_start_point(FloatPoint start_point)
{
m_p0 = start_point;
}
void set_end_point(FloatPoint end_point)
{
m_p1 = end_point;
}
private:
virtual void paint(IntRect physical_bounding_box, PaintFunction paint) const override;
SVGLinearGradientPaintStyle(FloatPoint p0, FloatPoint p1)
: m_p0(p0)
, m_p1(p1)
{
}
FloatPoint m_p0;
FloatPoint m_p1;
};
}