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:
parent
49497044de
commit
fdcc73d4b1
4 changed files with 129 additions and 66 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue