1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 02:17:34 +00:00

LibWeb: Paint conic-gradient()s

This is a first pass at painting conic-gradient()s, I've yet to try to
optimize this much, but I feel like you could do better than atan2
in a loop.
This commit is contained in:
MacDue 2022-10-30 19:28:00 +01:00 committed by Linus Groh
parent 49497044de
commit fdcc73d4b1
4 changed files with 129 additions and 66 deletions

View file

@ -1976,12 +1976,17 @@ String ConicGradientStyleValue::to_string() const
return builder.to_string();
}
void ConicGradientStyleValue::resolve_for_size(Layout::Node const&, Gfx::FloatSize const&) const
void ConicGradientStyleValue::resolve_for_size(Layout::Node const& node, Gfx::FloatSize const& size) const
{
if (!m_resolved.has_value())
m_resolved = ResolvedData { Painting::resolve_conic_gradient_data(node, *this), {} };
m_resolved->position = m_position.resolved(node, Gfx::FloatRect { { 0, 0 }, size });
}
void ConicGradientStyleValue::paint(PaintContext&, Gfx::IntRect const&, CSS::ImageRendering) const
void ConicGradientStyleValue::paint(PaintContext& context, Gfx::IntRect const& dest_rect, CSS::ImageRendering) const
{
VERIFY(m_resolved.has_value());
Painting::paint_conic_gradient(context, dest_rect, m_resolved->data, m_resolved->position.to_rounded<int>());
}
bool ConicGradientStyleValue::equals(StyleValue const&) const

View file

@ -1233,6 +1233,13 @@ private:
Angle m_from_angle;
PositionValue m_position;
Vector<AngularColorStopListElement> m_color_stop_list;
struct ResolvedData {
Painting::ConicGradientData data;
Gfx::FloatPoint position;
};
mutable Optional<ResolvedData> m_resolved;
};
class LinearGradientStyleValue final : public AbstractImageStyleValue {

View file

@ -33,10 +33,8 @@ static float calulate_gradient_length(Gfx::IntSize const& gradient_size, float g
return calulate_gradient_length(gradient_size, sin_angle, cos_angle);
}
LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::FloatSize const& gradient_size, CSS::LinearGradientStyleValue const& linear_gradient)
static ColorStopList resolve_color_stop_positions(auto const& color_stop_list, float gradient_length, auto resolve_position_to_float)
{
auto& color_stop_list = linear_gradient.color_stop_list();
VERIFY(color_stop_list.size() >= 2);
ColorStopList resolved_color_stops;
@ -55,26 +53,23 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F
resolved_color_stops.append(resolved_stop);
}
auto gradient_angle = linear_gradient.angle_degrees(gradient_size);
auto gradient_length_px = calulate_gradient_length(gradient_size.to_rounded<int>(), gradient_angle);
auto gradient_length = CSS::Length::make_px(gradient_length_px);
// 1. If the first color stop does not have a position, set its position to 0%.
resolved_color_stops.first().position = 0;
// If the last color stop does not have a position, set its position to 100%
resolved_color_stops.last().position = gradient_length_px;
resolved_color_stops.last().position = gradient_length;
// 2. If a color stop or transition hint has a position that is less than the
// specified position of any color stop or transition hint before it in the list,
// set its position to be equal to the largest specified position of any color stop
// or transition hint before it.
auto max_previous_color_stop_or_hint = resolved_color_stops[0].position;
auto resolve_stop_position = [&](auto& length_percentage) {
float value = length_percentage.resolved(node, gradient_length).to_px(node);
auto resolve_stop_position = [&](auto& position) {
float value = resolve_position_to_float(position);
value = max(value, max_previous_color_stop_or_hint);
max_previous_color_stop_or_hint = value;
return value;
};
// Move this step somewhere generic (since I think this code can be mostly reused for conic gradients)
size_t resolved_index = 0;
for (auto& stop : color_stop_list) {
if (stop.transition_hint.has_value())
@ -127,6 +122,19 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F
}
}
return resolved_color_stops;
}
LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::FloatSize const& gradient_size, CSS::LinearGradientStyleValue const& linear_gradient)
{
auto gradient_angle = linear_gradient.angle_degrees(gradient_size);
auto gradient_length_px = calulate_gradient_length(gradient_size.to_rounded<int>(), gradient_angle);
auto gradient_length = CSS::Length::make_px(gradient_length_px);
auto resolved_color_stops = resolve_color_stop_positions(linear_gradient.color_stop_list(), gradient_length_px, [&](auto const& length_percentage) {
return length_percentage.resolved(node, gradient_length).to_px(node);
});
Optional<float> repeat_length = {};
if (linear_gradient.is_repeating())
repeat_length = resolved_color_stops.last().position - resolved_color_stops.first().position;
@ -134,24 +142,17 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F
return { gradient_angle, resolved_color_stops, repeat_length };
}
void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_rect, LinearGradientData const& data)
ConicGradientData resolve_conic_gradient_data(Layout::Node const& node, CSS::ConicGradientStyleValue const& conic_gradient)
{
float angle = normalized_gradient_angle_radians(data.gradient_angle);
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
CSS::Angle one_turn(360.0f, CSS::Angle::Type::Deg);
auto resolved_color_stops = resolve_color_stop_positions(conic_gradient.color_stop_list(), one_turn.to_degrees(), [&](auto const& angle_percentage) {
return angle_percentage.resolved(node, one_turn).to_degrees();
});
return { conic_gradient.angle_degrees(), resolved_color_stops };
}
// Full length of the gradient
auto length = calulate_gradient_length(gradient_rect.size(), sin_angle, cos_angle);
Gfx::FloatPoint offset { cos_angle * (length / 2), sin_angle * (length / 2) };
auto center = gradient_rect.translated(-gradient_rect.location()).center();
auto start_point = center.to_type<float>() - offset;
// Rotate gradient line to be horizontal
auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
auto color_stop_step = [&](auto& previous_stop, auto& next_stop, float position) -> float {
static float color_stop_step(ColorStop const& previous_stop, ColorStop const& next_stop, float position)
{
if (position < previous_stop.position)
return 0;
if (position > next_stop.position)
@ -175,40 +176,66 @@ void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_r
// The color at that point is then a linear blend between the colors of the two color stops,
// blending (1 - C) of the first stop and C of the second stop.
return c;
};
Vector<Gfx::Color, 1024> gradient_line_colors;
auto gradient_color_count = round_to<int>(data.repeat_length.value_or(length));
gradient_line_colors.resize(gradient_color_count);
auto& color_stops = data.color_stops;
auto start_offset = data.repeat_length.has_value() ? color_stops.first().position : 0.0f;
auto start_offset_int = round_to<int>(start_offset);
}
class GradientLine {
public:
GradientLine(int length, int start_offset, Span<ColorStop const> color_stops)
{
m_gradient_line_colors.resize(length);
// Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in:
// https://drafts.csswg.org/css-images/#coloring-gradient-line
for (int loc = 0; loc < gradient_color_count; loc++) {
for (int loc = 0; loc < length; loc++) {
Gfx::Color gradient_color = color_stops[0].color.mixed_with(
color_stops[1].color,
color_stop_step(
color_stops[0],
color_stops[1],
loc + start_offset_int));
loc + start_offset));
for (size_t i = 1; i < color_stops.size() - 1; i++) {
gradient_color = gradient_color.mixed_with(
color_stops[i + 1].color,
color_stop_step(
color_stops[i],
color_stops[i + 1],
loc + start_offset_int));
loc + start_offset));
}
m_gradient_line_colors[loc] = gradient_color;
}
gradient_line_colors[loc] = gradient_color;
}
auto lookup_color = [&](int loc) {
return gradient_line_colors[clamp(loc, 0, gradient_color_count - 1)];
Gfx::Color lookup_color(int loc) const
{
return m_gradient_line_colors[clamp(loc, 0, m_gradient_line_colors.size() - 1)];
}
private:
Vector<Gfx::Color, 1024> m_gradient_line_colors;
};
void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_rect, LinearGradientData const& data)
{
float angle = normalized_gradient_angle_radians(data.gradient_angle);
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
// Full length of the gradient
auto length = calulate_gradient_length(gradient_rect.size(), sin_angle, cos_angle);
Gfx::FloatPoint offset { cos_angle * (length / 2), sin_angle * (length / 2) };
auto center = gradient_rect.translated(-gradient_rect.location()).center();
auto start_point = center.to_type<float>() - offset;
// Rotate gradient line to be horizontal
auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
auto gradient_color_count = round_to<int>(data.repeat_length.value_or(length));
auto& color_stops = data.color_stops;
auto start_offset = data.repeat_length.has_value() ? color_stops.first().position : 0.0f;
GradientLine gradient_line(gradient_color_count, round_to<int>(start_offset), color_stops);
auto repeat_wrap_if_required = [&](float loc) {
if (data.repeat_length.has_value())
loc = AK::fmod(loc + length, *data.repeat_length);
@ -220,7 +247,24 @@ void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_r
auto loc = repeat_wrap_if_required((x * cos_angle - (gradient_rect.height() - y) * -sin_angle) - rotated_start_point_x - start_offset);
auto blend = loc - static_cast<int>(loc);
// Blend between the two neighbouring colors (this fixes some nasty aliasing issues at small angles)
auto gradient_color = lookup_color(loc).mixed_with(lookup_color(repeat_wrap_if_required(loc + 1)), blend);
auto gradient_color = gradient_line.lookup_color(loc).mixed_with(gradient_line.lookup_color(repeat_wrap_if_required(loc + 1)), blend);
context.painter().set_pixel(gradient_rect.x() + x, gradient_rect.y() + y, gradient_color, gradient_color.alpha() < 255);
}
}
}
void paint_conic_gradient(PaintContext& context, Gfx::IntRect const& gradient_rect, ConicGradientData const& data, Gfx::IntPoint position)
{
// FIXME: Do we need/want sub-degree accuracy for the gradient line?
GradientLine gradient_line(360, 0, data.color_stops);
float start_angle = (360.0f - data.start_angle) + 90.0f;
for (int y = 0; y < gradient_rect.height(); y++) {
for (int x = 0; x < gradient_rect.width(); x++) {
auto point = Gfx::IntPoint { x, y } - position;
// FIXME: We could probably get away with some approximation here:
float loc = fmod((AK::atan2(float(point.y()), float(point.x())) * 180.0f / AK::Pi<float> + 360.0f + start_angle), 360.0f);
auto blend = loc - static_cast<int>(loc);
auto gradient_color = gradient_line.lookup_color(loc).mixed_with(gradient_line.lookup_color(loc + 1), blend);
context.painter().set_pixel(gradient_rect.x() + x, gradient_rect.y() + y, gradient_color, gradient_color.alpha() < 255);
}
}

View file

@ -28,8 +28,15 @@ struct LinearGradientData {
Optional<float> repeat_length;
};
struct ConicGradientData {
float start_angle;
ColorStopList color_stops;
};
LinearGradientData resolve_linear_gradient_data(Layout::Node const&, Gfx::FloatSize const&, CSS::LinearGradientStyleValue const&);
ConicGradientData resolve_conic_gradient_data(Layout::Node const&, CSS::ConicGradientStyleValue const&);
void paint_linear_gradient(PaintContext&, Gfx::IntRect const&, LinearGradientData const&);
void paint_conic_gradient(PaintContext&, Gfx::IntRect const&, ConicGradientData const&, Gfx::IntPoint position);
}