LibWeb: Handle degenerate repeating gradients
Normalize zero-length repeating gradients to a solid average color before painting code expands or tiles their stops. CSS Images defines this case as an average over the same color stops with an arbitrary non-zero distance, avoiding division by a zero repeat length while painting. Add a reduced crash test covering zero-length repeating linear, radial, and conic gradients.
This commit is contained in:
parent
613a04e4df
commit
e18d78eb2d
2 changed files with 80 additions and 0 deletions
|
|
@ -94,6 +94,9 @@ static ColorStopList replace_transition_hints_with_normal_color_stops(ColorStopL
|
|||
|
||||
static ColorStopList expand_repeat_length(ColorStopList const& color_stop_list, float repeat_length)
|
||||
{
|
||||
VERIFY(isfinite(repeat_length));
|
||||
VERIFY(repeat_length > 0);
|
||||
|
||||
// https://drafts.csswg.org/css-images/#repeating-gradients
|
||||
// When rendered, however, the color-stops are repeated infinitely in both directions, with their
|
||||
// positions shifted by multiples of the difference between the last specified color-stop's position
|
||||
|
|
@ -141,6 +144,64 @@ static ColorStopList expand_repeat_length(ColorStopList const& color_stop_list,
|
|||
return color_stop_list_with_expanded_repeat;
|
||||
}
|
||||
|
||||
static bool has_degenerate_repeat_length(ResolvedColorStopData const& resolved_color_stops)
|
||||
{
|
||||
return resolved_color_stops.repeat_length.has_value()
|
||||
&& (!isfinite(*resolved_color_stops.repeat_length) || *resolved_color_stops.repeat_length <= 0);
|
||||
}
|
||||
|
||||
static Gfx::Color average_color_for_degenerate_repeating_gradient(ColorStopList const& color_stop_list)
|
||||
{
|
||||
VERIFY(!color_stop_list.is_empty());
|
||||
|
||||
if (color_stop_list.size() == 1)
|
||||
return color_stop_list.first().color;
|
||||
|
||||
// https://drafts.csswg.org/css-images-3/#repeating-gradients
|
||||
// For zero-length repeating gradients, average a gradient with the same
|
||||
// colors and equally-spaced stops over an arbitrary non-zero distance.
|
||||
float premultiplied_red = 0;
|
||||
float premultiplied_green = 0;
|
||||
float premultiplied_blue = 0;
|
||||
float alpha = 0;
|
||||
float const weight = 0.5f / static_cast<float>(color_stop_list.size() - 1);
|
||||
|
||||
auto add_weighted_color = [&](Gfx::Color color) {
|
||||
auto color_alpha = color.alpha() / 255.0f;
|
||||
alpha += color_alpha * weight;
|
||||
premultiplied_red += color.red() / 255.0f * color_alpha * weight;
|
||||
premultiplied_green += color.green() / 255.0f * color_alpha * weight;
|
||||
premultiplied_blue += color.blue() / 255.0f * color_alpha * weight;
|
||||
};
|
||||
|
||||
for (size_t i = 1; i < color_stop_list.size(); ++i) {
|
||||
add_weighted_color(color_stop_list[i - 1].color);
|
||||
add_weighted_color(color_stop_list[i].color);
|
||||
}
|
||||
|
||||
if (alpha == 0)
|
||||
return Gfx::Color::Transparent;
|
||||
|
||||
auto normalized_to_u8 = [](float value) -> u8 {
|
||||
return clamp(lroundf(value * 255.0f), 0L, 255L);
|
||||
};
|
||||
|
||||
return {
|
||||
normalized_to_u8(premultiplied_red / alpha),
|
||||
normalized_to_u8(premultiplied_green / alpha),
|
||||
normalized_to_u8(premultiplied_blue / alpha),
|
||||
normalized_to_u8(alpha),
|
||||
};
|
||||
}
|
||||
|
||||
static void normalize_degenerate_repeating_gradient(ResolvedColorStopData& resolved_color_stops)
|
||||
{
|
||||
auto average_color = average_color_for_degenerate_repeating_gradient(resolved_color_stops.list);
|
||||
resolved_color_stops.list = { { average_color, 0 }, { average_color, 1 } };
|
||||
resolved_color_stops.repeat_length = {};
|
||||
resolved_color_stops.repeating = false;
|
||||
}
|
||||
|
||||
static ColorStopList expand_color_stops_for_painting(ColorStopList const& color_stop_list, Optional<float> repeat_length)
|
||||
{
|
||||
auto expanded = repeat_length.has_value()
|
||||
|
|
@ -258,6 +319,9 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod
|
|||
},
|
||||
linear_gradient.is_repeating());
|
||||
|
||||
if (has_degenerate_repeat_length(resolved_color_stops))
|
||||
normalize_degenerate_repeating_gradient(resolved_color_stops);
|
||||
|
||||
// Replace transition hints for painting; keep repeat_length for Skia's native tiling
|
||||
resolved_color_stops.list = replace_transition_hints_with_normal_color_stops(resolved_color_stops.list);
|
||||
|
||||
|
|
@ -278,6 +342,9 @@ ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node,
|
|||
},
|
||||
conic_gradient.is_repeating());
|
||||
|
||||
if (has_degenerate_repeat_length(resolved_color_stops))
|
||||
normalize_degenerate_repeating_gradient(resolved_color_stops);
|
||||
|
||||
// Expand color stops for painting (replace transition hints and expand repeat length)
|
||||
resolved_color_stops.list = expand_color_stops_for_painting(resolved_color_stops.list, resolved_color_stops.repeat_length);
|
||||
resolved_color_stops.repeat_length = {};
|
||||
|
|
@ -297,6 +364,9 @@ RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& nod
|
|||
},
|
||||
radial_gradient.is_repeating());
|
||||
|
||||
if (has_degenerate_repeat_length(resolved_color_stops))
|
||||
normalize_degenerate_repeating_gradient(resolved_color_stops);
|
||||
|
||||
// Expand color stops for painting (replace transition hints and expand repeat length)
|
||||
resolved_color_stops.list = expand_color_stops_for_painting(resolved_color_stops.list, resolved_color_stops.repeat_length);
|
||||
resolved_color_stops.repeat_length = {};
|
||||
|
|
|
|||
10
Tests/LibWeb/Crash/CSS/repeating-gradient-zero-length.html
Normal file
10
Tests/LibWeb/Crash/CSS/repeating-gradient-zero-length.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<style>
|
||||
.gradient {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<div class="gradient" style="background: repeating-linear-gradient(red 0%, blue 0%)"></div>
|
||||
<div class="gradient" style="background: repeating-radial-gradient(circle, red 0%, blue 0%)"></div>
|
||||
<div class="gradient" style="background: repeating-conic-gradient(red 0deg, blue 0deg)"></div>
|
||||
Loading…
Reference in a new issue