Browse Source

LibWeb: Handle gradient repeating as defined in the spec in Skia painter

Couple fixes found by reading the spec:
- Repeating should also happen in negative direction, so the whole
  [0, 1] is covered.
- Leftmost and rightmost stops should be clamped to [0, 1] range if
  needed, because Skia ignores everything outside of this range.
Aliaksandr Kalenik 1 năm trước cách đây
mục cha
commit
dadf82f2a2
1 tập tin đã thay đổi với 39 bổ sung12 xóa
  1. 39 12
      Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp

+ 39 - 12
Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp

@@ -583,22 +583,49 @@ static ColorStopList replace_transition_hints_with_normal_color_stops(ColorStopL
 
 static ColorStopList expand_repeat_length(ColorStopList const& color_stop_list, float repeat_length)
 {
-    ColorStopList color_stop_list_with_expanded_repeat;
-    size_t repeat_count = 0;
-    while (true) {
+    // 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
+    // and the first specified color-stop’s position. For example, repeating-linear-gradient(red 10px, blue 50px)
+    // is equivalent to linear-gradient(..., red -30px, blue 10px, red 10px, blue 50px, red 50px, blue 90px, ...).
+
+    auto first_stop_position = color_stop_list.first().position;
+    int const negative_repeat_count = AK::ceil(first_stop_position / repeat_length);
+    int const positive_repeat_count = AK::ceil((1.0f - first_stop_position) / repeat_length);
+
+    ColorStopList color_stop_list_with_expanded_repeat = color_stop_list;
+
+    auto get_color_between_stops = [](float position, auto const& current_stop, auto const& previous_stop) {
+        auto distance_between_stops = current_stop.position - previous_stop.position;
+        auto percentage = (position - previous_stop.position) / distance_between_stops;
+        return previous_stop.color.interpolate(current_stop.color, percentage);
+    };
+
+    for (auto repeat_count = 1; repeat_count <= negative_repeat_count; repeat_count++) {
+        for (auto stop : color_stop_list.in_reverse()) {
+            stop.position += repeat_length * static_cast<float>(-repeat_count);
+            if (stop.position < 0) {
+                stop.color = get_color_between_stops(0.0f, stop, color_stop_list_with_expanded_repeat.first());
+                color_stop_list_with_expanded_repeat.prepend(stop);
+                break;
+            }
+            color_stop_list_with_expanded_repeat.prepend(stop);
+        }
+    }
+
+    for (auto repeat_count = 0; repeat_count < positive_repeat_count; repeat_count++) {
         for (auto stop : color_stop_list) {
-            stop.position += repeat_length * repeat_count;
-            if (stop.position > 1.0f)
-                return color_stop_list_with_expanded_repeat;
+            stop.position += repeat_length * static_cast<float>(repeat_count);
+            if (stop.position > 1) {
+                stop.color = get_color_between_stops(1.0f, stop, color_stop_list_with_expanded_repeat.last());
+                color_stop_list_with_expanded_repeat.append(stop);
+                break;
+            }
             color_stop_list_with_expanded_repeat.append(stop);
         }
-        repeat_count++;
-        if (repeat_count > 1000) {
-            // Crash instead of being stuck in infinite loop
-            VERIFY_NOT_REACHED();
-        }
     }
-    VERIFY_NOT_REACHED();
+
+    return color_stop_list_with_expanded_repeat;
 }
 
 CommandResult DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command)