LibGfx: Add support for SVG repeat/reflect gradients

This commit is contained in:
MacDue 2023-08-19 19:12:00 +01:00 committed by Andreas Kling
parent a48b2c1c66
commit b9294e5fdf
Notes: sideshowbarker 2024-07-19 01:59:31 +09:00
2 changed files with 69 additions and 5 deletions

View file

@ -53,8 +53,8 @@ enum class UsePremultipliedAlpha {
class GradientLine { class GradientLine {
public: public:
GradientLine(int gradient_length, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length, UsePremultipliedAlpha use_premultiplied_alpha = UsePremultipliedAlpha::Yes) GradientLine(int gradient_length, ReadonlySpan<ColorStop> color_stops, Optional<float> repeat_length, UsePremultipliedAlpha use_premultiplied_alpha = UsePremultipliedAlpha::Yes)
: m_repeating(repeat_length.has_value()) : m_repeat_mode(repeat_length.has_value() ? RepeatMode::Repeat : RepeatMode::None)
, m_start_offset(round_to<int>((m_repeating ? color_stops.first().position : 0.0f) * gradient_length)) , m_start_offset(round_to<int>((repeating() ? color_stops.first().position : 0.0f) * gradient_length))
, m_color_stops(color_stops) , m_color_stops(color_stops)
, m_use_premultiplied_alpha(use_premultiplied_alpha) , m_use_premultiplied_alpha(use_premultiplied_alpha)
{ {
@ -104,8 +104,18 @@ public:
if (m_sample_scale != 1.0f) if (m_sample_scale != 1.0f)
loc *= m_sample_scale; loc *= m_sample_scale;
auto repeat_wrap_if_required = [&](i64 loc) { auto repeat_wrap_if_required = [&](i64 loc) {
if (m_repeating) if (m_repeat_mode != RepeatMode::None) {
return (loc + m_start_offset) % static_cast<i64>(m_gradient_line_colors.size()); auto current_loc = loc + m_start_offset;
auto gradient_len = static_cast<i64>(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; return loc;
}; };
auto int_loc = static_cast<i64>(floor(loc)); auto int_loc = static_cast<i64>(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: private:
bool m_repeating { false }; RepeatMode m_repeat_mode { RepeatMode::None };
int m_start_offset { 0 }; int m_start_offset { 0 };
float m_sample_scale { 1 }; float m_sample_scale { 1 };
ReadonlySpan<ColorStop> m_color_stops {}; ReadonlySpan<ColorStop> m_color_stops {};
@ -161,6 +189,11 @@ struct Gradient {
}; };
} }
GradientLine& gradient_line()
{
return m_gradient_line;
}
private: private:
GradientLine m_gradient_line; GradientLine m_gradient_line;
TransformFunction m_transform_function; 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())); 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) void SVGGradientPaintStyle::set_gradient_transform(AffineTransform transform)
{ {
// Note: The scaling is removed so enough points on the gradient line are generated. // 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( auto linear_gradient = make_linear_gradient_between_two_points(
m_p0.scaled(scale, scale), m_p1.scaled(scale, scale), m_p0.scaled(scale, scale), m_p1.scaled(scale, scale),
color_stops(), repeat_length()); 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<float>()](IntPoint target_point) { paint([&, sampler = linear_gradient.sample_function<float>()](IntPoint target_point) {
auto point = target_point.translated(physical_bounding_box.location()).to_type<float>(); auto point = target_point.translated(physical_bounding_box.location()).to_type<float>();
@ -511,6 +560,8 @@ void SVGRadialGradientPaintStyle::paint(IntRect physical_bounding_box, PaintFunc
auto radial_gradient = create_radial_gradient_between_two_circles( 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, m_start_center.scaled(scale, scale), m_start_radius * scale, m_end_center.scaled(scale, scale), m_end_radius * scale,
color_stops(), repeat_length()); 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<float>()](IntPoint target_point) { paint([&, sampler = radial_gradient.sample_function<float>()](IntPoint target_point) {
auto point = target_point.translated(physical_bounding_box.location()).to_type<float>(); auto point = target_point.translated(physical_bounding_box.location()).to_type<float>();

View file

@ -246,13 +246,26 @@ class SVGGradientPaintStyle : public GradientPaintStyle {
public: public:
void set_gradient_transform(Gfx::AffineTransform transform); 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: protected:
Optional<AffineTransform> const& scale_adjusted_inverse_gradient_transform() const { return m_inverse_transform; } Optional<AffineTransform> const& scale_adjusted_inverse_gradient_transform() const { return m_inverse_transform; }
float gradient_transform_scale() const { return m_scale; } float gradient_transform_scale() const { return m_scale; }
SpreadMethod spread_method() const { return m_spread_method; }
private: private:
Optional<AffineTransform> m_inverse_transform {}; Optional<AffineTransform> m_inverse_transform {};
float m_scale = 1.0f; float m_scale = 1.0f;
SpreadMethod m_spread_method { SpreadMethod::Pad };
}; };
class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle { class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle {