diff --git a/Userland/Libraries/LibGfx/GradientPainting.cpp b/Userland/Libraries/LibGfx/GradientPainting.cpp index b7e8e1a0bf..bb2e02449d 100644 --- a/Userland/Libraries/LibGfx/GradientPainting.cpp +++ b/Userland/Libraries/LibGfx/GradientPainting.cpp @@ -53,8 +53,8 @@ enum class UsePremultipliedAlpha { class GradientLine { public: GradientLine(int gradient_length, ReadonlySpan color_stops, Optional repeat_length, UsePremultipliedAlpha use_premultiplied_alpha = UsePremultipliedAlpha::Yes) - : m_repeating(repeat_length.has_value()) - , m_start_offset(round_to((m_repeating ? color_stops.first().position : 0.0f) * gradient_length)) + : m_repeat_mode(repeat_length.has_value() ? RepeatMode::Repeat : RepeatMode::None) + , m_start_offset(round_to((repeating() ? color_stops.first().position : 0.0f) * gradient_length)) , m_color_stops(color_stops) , m_use_premultiplied_alpha(use_premultiplied_alpha) { @@ -104,8 +104,18 @@ public: if (m_sample_scale != 1.0f) loc *= m_sample_scale; auto repeat_wrap_if_required = [&](i64 loc) { - if (m_repeating) - return (loc + m_start_offset) % static_cast(m_gradient_line_colors.size()); + if (m_repeat_mode != RepeatMode::None) { + auto current_loc = loc + m_start_offset; + auto gradient_len = static_cast(m_gradient_line_colors.size()); + if (m_repeat_mode == RepeatMode::Repeat) { + auto color_loc = current_loc % gradient_len; + return color_loc < 0 ? gradient_len + color_loc : color_loc; + } else if (m_repeat_mode == RepeatMode::Reflect) { + auto color_loc = AK::abs(current_loc % gradient_len); + auto repeats = current_loc / gradient_len; + return (repeats & 1) ? gradient_len - color_loc : color_loc; + } + } return loc; }; auto int_loc = static_cast(floor(loc)); @@ -129,8 +139,26 @@ public: } } + bool repeating() const + { + return m_repeat_mode != RepeatMode::None; + } + + enum class RepeatMode { + None, + Repeat, + Reflect + }; + + void set_repeat_mode(RepeatMode repeat_mode) + { + // Note: A gradient can be set to repeating without a repeat length. + // The repeat length is used for CSS gradients but not for SVG gradients. + m_repeat_mode = repeat_mode; + } + private: - bool m_repeating { false }; + RepeatMode m_repeat_mode { RepeatMode::None }; int m_start_offset { 0 }; float m_sample_scale { 1 }; ReadonlySpan m_color_stops {}; @@ -161,6 +189,11 @@ struct Gradient { }; } + GradientLine& gradient_line() + { + return m_gradient_line; + } + private: GradientLine m_gradient_line; TransformFunction m_transform_function; @@ -340,6 +373,20 @@ void CanvasLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintF paint(make_sample_non_relative(physical_bounding_box.location(), linear_gradient.sample_function())); } +static GradientLine::RepeatMode svg_spread_method_to_repeat_mode(SVGGradientPaintStyle::SpreadMethod spread_method) +{ + switch (spread_method) { + case SVGGradientPaintStyle::SpreadMethod::Pad: + return GradientLine::RepeatMode::None; + case SVGGradientPaintStyle::SpreadMethod::Reflect: + return GradientLine::RepeatMode::Reflect; + case SVGGradientPaintStyle::SpreadMethod::Repeat: + return GradientLine::RepeatMode::Repeat; + default: + VERIFY_NOT_REACHED(); + } +} + void SVGGradientPaintStyle::set_gradient_transform(AffineTransform transform) { // Note: The scaling is removed so enough points on the gradient line are generated. @@ -369,6 +416,8 @@ void SVGLinearGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunc auto linear_gradient = make_linear_gradient_between_two_points( m_p0.scaled(scale, scale), m_p1.scaled(scale, scale), color_stops(), repeat_length()); + linear_gradient.gradient_line().set_repeat_mode( + svg_spread_method_to_repeat_mode(spread_method())); paint([&, sampler = linear_gradient.sample_function()](IntPoint target_point) { auto point = target_point.translated(physical_bounding_box.location()).to_type(); @@ -511,6 +560,8 @@ void SVGRadialGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunc auto radial_gradient = create_radial_gradient_between_two_circles( m_start_center.scaled(scale, scale), m_start_radius * scale, m_end_center.scaled(scale, scale), m_end_radius * scale, color_stops(), repeat_length()); + radial_gradient.gradient_line().set_repeat_mode( + svg_spread_method_to_repeat_mode(spread_method())); paint([&, sampler = radial_gradient.sample_function()](IntPoint target_point) { auto point = target_point.translated(physical_bounding_box.location()).to_type(); diff --git a/Userland/Libraries/LibGfx/PaintStyle.h b/Userland/Libraries/LibGfx/PaintStyle.h index 3aac3a015e..73c64d884a 100644 --- a/Userland/Libraries/LibGfx/PaintStyle.h +++ b/Userland/Libraries/LibGfx/PaintStyle.h @@ -246,13 +246,26 @@ class SVGGradientPaintStyle : public GradientPaintStyle { public: void set_gradient_transform(Gfx::AffineTransform transform); + enum class SpreadMethod { + Pad, + Repeat, + Reflect + }; + + void set_spread_method(SpreadMethod spread_method) + { + m_spread_method = spread_method; + } + protected: Optional const& scale_adjusted_inverse_gradient_transform() const { return m_inverse_transform; } float gradient_transform_scale() const { return m_scale; } + SpreadMethod spread_method() const { return m_spread_method; } private: Optional m_inverse_transform {}; float m_scale = 1.0f; + SpreadMethod m_spread_method { SpreadMethod::Pad }; }; class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle {