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();
|
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
|
bool ConicGradientStyleValue::equals(StyleValue const&) const
|
||||||
|
|
|
@ -1233,6 +1233,13 @@ private:
|
||||||
Angle m_from_angle;
|
Angle m_from_angle;
|
||||||
PositionValue m_position;
|
PositionValue m_position;
|
||||||
Vector<AngularColorStopListElement> m_color_stop_list;
|
Vector<AngularColorStopListElement> m_color_stop_list;
|
||||||
|
|
||||||
|
struct ResolvedData {
|
||||||
|
Painting::ConicGradientData data;
|
||||||
|
Gfx::FloatPoint position;
|
||||||
|
};
|
||||||
|
|
||||||
|
mutable Optional<ResolvedData> m_resolved;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LinearGradientStyleValue final : public AbstractImageStyleValue {
|
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);
|
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);
|
VERIFY(color_stop_list.size() >= 2);
|
||||||
ColorStopList resolved_color_stops;
|
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);
|
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%.
|
// 1. If the first color stop does not have a position, set its position to 0%.
|
||||||
resolved_color_stops.first().position = 0;
|
resolved_color_stops.first().position = 0;
|
||||||
// If the last color stop does not have a position, set its position to 100%
|
// 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
|
// 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,
|
// 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
|
// set its position to be equal to the largest specified position of any color stop
|
||||||
// or transition hint before it.
|
// or transition hint before it.
|
||||||
auto max_previous_color_stop_or_hint = resolved_color_stops[0].position;
|
auto max_previous_color_stop_or_hint = resolved_color_stops[0].position;
|
||||||
auto resolve_stop_position = [&](auto& length_percentage) {
|
auto resolve_stop_position = [&](auto& position) {
|
||||||
float value = length_percentage.resolved(node, gradient_length).to_px(node);
|
float value = resolve_position_to_float(position);
|
||||||
value = max(value, max_previous_color_stop_or_hint);
|
value = max(value, max_previous_color_stop_or_hint);
|
||||||
max_previous_color_stop_or_hint = value;
|
max_previous_color_stop_or_hint = value;
|
||||||
return 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;
|
size_t resolved_index = 0;
|
||||||
for (auto& stop : color_stop_list) {
|
for (auto& stop : color_stop_list) {
|
||||||
if (stop.transition_hint.has_value())
|
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 = {};
|
Optional<float> repeat_length = {};
|
||||||
if (linear_gradient.is_repeating())
|
if (linear_gradient.is_repeating())
|
||||||
repeat_length = resolved_color_stops.last().position - resolved_color_stops.first().position;
|
repeat_length = resolved_color_stops.last().position - resolved_color_stops.first().position;
|
||||||
|
@ -134,6 +142,77 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F
|
||||||
return { gradient_angle, resolved_color_stops, repeat_length };
|
return { gradient_angle, resolved_color_stops, repeat_length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConicGradientData resolve_conic_gradient_data(Layout::Node const& node, CSS::ConicGradientStyleValue const& conic_gradient)
|
||||||
|
{
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return 1;
|
||||||
|
// For any given point between the two color stops,
|
||||||
|
// determine the point’s location as a percentage of the distance between the two color stops.
|
||||||
|
// Let this percentage be P.
|
||||||
|
auto stop_length = next_stop.position - previous_stop.position;
|
||||||
|
// FIXME: Avoids NaNs... Still not quite correct?
|
||||||
|
if (stop_length <= 0)
|
||||||
|
return 1;
|
||||||
|
auto p = (position - previous_stop.position) / stop_length;
|
||||||
|
if (!next_stop.transition_hint.has_value())
|
||||||
|
return p;
|
||||||
|
if (*next_stop.transition_hint >= 1)
|
||||||
|
return 0;
|
||||||
|
if (*next_stop.transition_hint <= 0)
|
||||||
|
return 1;
|
||||||
|
// Let C, the color weighting at that point, be equal to P^(logH(.5)).
|
||||||
|
auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint));
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 < 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));
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
m_gradient_line_colors[loc] = gradient_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_rect, LinearGradientData const& data)
|
||||||
{
|
{
|
||||||
float angle = normalized_gradient_angle_radians(data.gradient_angle);
|
float angle = normalized_gradient_angle_radians(data.gradient_angle);
|
||||||
|
@ -151,63 +230,11 @@ void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_r
|
||||||
// Rotate gradient line to be horizontal
|
// Rotate gradient line to be horizontal
|
||||||
auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
|
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 {
|
|
||||||
if (position < previous_stop.position)
|
|
||||||
return 0;
|
|
||||||
if (position > next_stop.position)
|
|
||||||
return 1;
|
|
||||||
// For any given point between the two color stops,
|
|
||||||
// determine the point’s location as a percentage of the distance between the two color stops.
|
|
||||||
// Let this percentage be P.
|
|
||||||
auto stop_length = next_stop.position - previous_stop.position;
|
|
||||||
// FIXME: Avoids NaNs... Still not quite correct?
|
|
||||||
if (stop_length <= 0)
|
|
||||||
return 1;
|
|
||||||
auto p = (position - previous_stop.position) / stop_length;
|
|
||||||
if (!next_stop.transition_hint.has_value())
|
|
||||||
return p;
|
|
||||||
if (*next_stop.transition_hint >= 1)
|
|
||||||
return 0;
|
|
||||||
if (*next_stop.transition_hint <= 0)
|
|
||||||
return 1;
|
|
||||||
// Let C, the color weighting at that point, be equal to P^(logH(.5)).
|
|
||||||
auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint));
|
|
||||||
// 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));
|
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& color_stops = data.color_stops;
|
||||||
auto start_offset = data.repeat_length.has_value() ? color_stops.first().position : 0.0f;
|
auto start_offset = data.repeat_length.has_value() ? color_stops.first().position : 0.0f;
|
||||||
auto start_offset_int = round_to<int>(start_offset);
|
|
||||||
|
|
||||||
// Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in:
|
GradientLine gradient_line(gradient_color_count, round_to<int>(start_offset), color_stops);
|
||||||
// https://drafts.csswg.org/css-images/#coloring-gradient-line
|
|
||||||
|
|
||||||
for (int loc = 0; loc < gradient_color_count; 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));
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
gradient_line_colors[loc] = gradient_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lookup_color = [&](int loc) {
|
|
||||||
return gradient_line_colors[clamp(loc, 0, gradient_color_count - 1)];
|
|
||||||
};
|
|
||||||
|
|
||||||
auto repeat_wrap_if_required = [&](float loc) {
|
auto repeat_wrap_if_required = [&](float loc) {
|
||||||
if (data.repeat_length.has_value())
|
if (data.repeat_length.has_value())
|
||||||
|
@ -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 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);
|
auto blend = loc - static_cast<int>(loc);
|
||||||
// Blend between the two neighbouring colors (this fixes some nasty aliasing issues at small angles)
|
// 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);
|
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;
|
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&);
|
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_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