diff --git a/Userland/Libraries/LibGfx/GradientPainting.cpp b/Userland/Libraries/LibGfx/GradientPainting.cpp index 4f50f2610c..7d1cd65c60 100644 --- a/Userland/Libraries/LibGfx/GradientPainting.cpp +++ b/Userland/Libraries/LibGfx/GradientPainting.cpp @@ -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 color_stops, Optional 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()) diff --git a/Userland/Libraries/LibGfx/PaintStyle.h b/Userland/Libraries/LibGfx/PaintStyle.h index 729e9b4285..b39e509980 100644 --- a/Userland/Libraries/LibGfx/PaintStyle.h +++ b/Userland/Libraries/LibGfx/PaintStyle.h @@ -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 const& gradient_transform() const { return m_gradient_transform; } + +private: + Optional m_gradient_transform {}; +}; + +class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle { +public: + static ErrorOr> 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; +}; + }