LibWeb: Repeat shader for repeating linear gradient

We implemented repeating linear gradients by expanding a vector of color
stops until the entire range was covered. This is both a bit wasteful
and caused Skia to draw corrupted gradients to screen whenever the total
amount of color stops and positions exceeded 127.

Instead of doing that, use the original color stops for the shader and
repeat it instead of clamping it. We need to do a bit of math to project
positions correctly, but after that the shader repeats itself nicely.

While we're here, calculate the gradient's length and the center point
as floats instead of ints, yielding a slight but noticeable improvement
in gradient rendering (see the diff on the zig zag pattern in
css-gradients.html for an example of this).
This commit is contained in:
Jelle Raaijmakers 2025-10-21 17:02:16 +02:00 committed by Jelle Raaijmakers
parent 8af6da64a6
commit f8c4043460
5 changed files with 20 additions and 19 deletions

View File

@ -399,41 +399,39 @@ static SkGradientShader::Interpolation to_skia_interpolation(CSS::InterpolationM
void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command)
{
auto const& linear_gradient_data = command.linear_gradient_data;
auto color_stop_list = linear_gradient_data.color_stops.list;
auto const& color_stop_list = linear_gradient_data.color_stops.list;
auto const& repeat_length = linear_gradient_data.color_stops.repeat_length;
VERIFY(!color_stop_list.is_empty());
if (repeat_length.has_value())
color_stop_list = expand_repeat_length(color_stop_list, *repeat_length);
auto stops_with_replaced_transition_hints = replace_transition_hints_with_normal_color_stops(color_stop_list);
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
auto const first_position = repeat_length.has_value() ? stops_with_replaced_transition_hints.first().position : 0.f;
for (size_t stop_index = 0; stop_index < stops_with_replaced_transition_hints.size(); stop_index++) {
auto const& stop = stops_with_replaced_transition_hints[stop_index];
if (stop_index > 0 && stop == stops_with_replaced_transition_hints[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append(stop.position);
positions.append((stop.position - first_position) / repeat_length.value_or(1));
}
auto const& rect = command.gradient_rect;
auto length = calculate_gradient_length<int>(rect.size(), linear_gradient_data.gradient_angle);
auto bottom = rect.center().translated(0, -length / 2);
auto top = rect.center().translated(0, length / 2);
auto rect = command.gradient_rect.to_type<float>();
auto length = calculate_gradient_length<float>(rect.size(), linear_gradient_data.gradient_angle);
Array points {
to_skia_point(top),
to_skia_point(bottom),
};
// Starting and ending points before rotation (0deg / "to top")
auto rect_center = rect.center();
auto start = rect_center.translated(0, (.5f - first_position) * length);
auto end = start.translated(0, repeat_length.value_or(1) * -length);
Array const points { to_skia_point(start), to_skia_point(end) };
auto center = to_skia_rect(rect).center();
SkMatrix matrix;
matrix.setRotate(linear_gradient_data.gradient_angle, center.x(), center.y());
matrix.setRotate(linear_gradient_data.gradient_angle, rect_center.x(), rect_center.y());
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(linear_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kClamp, interpolation, &matrix);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 KiB

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -1,11 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="match" href="../expected/css-background-clip-text-ref.html" />
<meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-25969">
<title>Document</title>
<meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-25811">
<style>
html {

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<link rel="match" href="../expected/css-gradients-ref.html" />
<meta name="fuzzy" content="maxDifference=0-63;totalPixels=0-215">
<meta name="fuzzy" content="maxDifference=0-63;totalPixels=0-167">
<style>
body {
background-color: white;
@ -145,6 +145,10 @@
background-image: -webkit-repeating-linear-gradient(left, red 10%, blue 30%);
}
.grad-repeat-4 {
background-image: repeating-linear-gradient(to bottom, blue 0 2px, red 2px 4px);
}
.grad-double-position {
background-image: linear-gradient(to right, red 20%, orange 20% 40%, yellow 40% 60%, green 60% 80%, blue 80%)
}
@ -246,6 +250,7 @@
<div class="box grad-conic-6"></div>
<div class="box grad-conic-repeat-1"></div>
<div class="box grad-conic-repeat-2"></div>
<div class="box grad-repeat-4"></div>
</section>
<section class="rects">
<div class="rect grad-7"></div>