Browse Source

LibWeb: Implement CSS filter painting

We can reuse our implementation for `backdrop-filter` and use it as a
painting filter for each stacking context.
Jelle Raaijmakers 8 months ago
parent
commit
1b9c50b664

+ 5 - 3
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -183,11 +183,13 @@ bool Node::establishes_stacking_context() const
     if (parent() && parent()->display().is_grid_inside() && computed_values().z_index().has_value())
         return true;
 
+    // https://drafts.fxtf.org/filter-effects/#FilterProperty
     // https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation
-    // A computed value of other than none results in the creation of both a stacking context [CSS21] and a Containing Block for absolute and fixed position descendants,
-    // unless the element it applies to is a document root element in the current browsing context.
+    // A computed value of other than none results in the creation of both a stacking context
+    // [CSS21] and a Containing Block for absolute and fixed position descendants, unless the
+    // element it applies to is a document root element in the current browsing context.
     // Spec Note: This rule works in the same way as for the filter property.
-    if (!computed_values().backdrop_filter().is_none())
+    if (!computed_values().backdrop_filter().is_none() || !computed_values().filter().is_none())
         return true;
 
     // Element with any of the following properties with value other than none:

+ 1 - 0
Userland/Libraries/LibWeb/Painting/Command.h

@@ -113,6 +113,7 @@ struct StackingContextTransform {
 
 struct PushStackingContext {
     float opacity;
+    CSS::ResolvedFilter filter;
     // The bounding box of the source paintable (pre-transform).
     Gfx::IntRect source_paintable_rect;
     // A translation to be applied after the stacking context has been transformed.

+ 153 - 126
Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp

@@ -235,6 +235,128 @@ static SkColor4f to_skia_color4f(Gfx::Color const& color)
     };
 }
 
+static sk_sp<SkImageFilter> to_skia_image_filter(CSS::ResolvedFilter::FilterFunction const& function)
+{
+    // See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
+    return function.visit(
+        [&](CSS::ResolvedFilter::Blur const& blur_filter) {
+            return SkImageFilters::Blur(blur_filter.radius, blur_filter.radius, nullptr);
+        },
+        [&](CSS::ResolvedFilter::Color const& color) {
+            auto amount = clamp(color.amount, 0.0f, 1.0f);
+
+            // Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
+            sk_sp<SkColorFilter> color_filter;
+            switch (color.type) {
+            case CSS::FilterOperation::Color::Type::Grayscale: {
+                float matrix[20] = {
+                    0.2126f + 0.7874f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
+                    0.2126f - 0.2126f * (1 - amount), 0.7152f + 0.2848f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
+                    0.2126f - 0.2126f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f + 0.9278f * (1 - amount), 0, 0,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Brightness: {
+                float matrix[20] = {
+                    amount, 0, 0, 0, 0,
+                    0, amount, 0, 0, 0,
+                    0, 0, amount, 0, 0,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Contrast: {
+                float intercept = -(0.5f * amount) + 0.5f;
+                float matrix[20] = {
+                    amount, 0, 0, 0, intercept,
+                    0, amount, 0, 0, intercept,
+                    0, 0, amount, 0, intercept,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Invert: {
+                float matrix[20] = {
+                    1 - 2 * amount, 0, 0, 0, amount,
+                    0, 1 - 2 * amount, 0, 0, amount,
+                    0, 0, 1 - 2 * amount, 0, amount,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Opacity: {
+                float matrix[20] = {
+                    1, 0, 0, 0, 0,
+                    0, 1, 0, 0, 0,
+                    0, 0, 1, 0, 0,
+                    0, 0, 0, amount, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Sepia: {
+                float matrix[20] = {
+                    0.393f + 0.607f * (1 - amount), 0.769f - 0.769f * (1 - amount), 0.189f - 0.189f * (1 - amount), 0, 0,
+                    0.349f - 0.349f * (1 - amount), 0.686f + 0.314f * (1 - amount), 0.168f - 0.168f * (1 - amount), 0, 0,
+                    0.272f - 0.272f * (1 - amount), 0.534f - 0.534f * (1 - amount), 0.131f + 0.869f * (1 - amount), 0, 0,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            case CSS::FilterOperation::Color::Type::Saturate: {
+                float matrix[20] = {
+                    0.213f + 0.787f * amount, 0.715f - 0.715f * amount, 0.072f - 0.072f * amount, 0, 0,
+                    0.213f - 0.213f * amount, 0.715f + 0.285f * amount, 0.072f - 0.072f * amount, 0, 0,
+                    0.213f - 0.213f * amount, 0.715f - 0.715f * amount, 0.072f + 0.928f * amount, 0, 0,
+                    0, 0, 0, 1, 0
+                };
+                color_filter = SkColorFilters::Matrix(matrix);
+                break;
+            }
+            default:
+                VERIFY_NOT_REACHED();
+            }
+
+            return SkImageFilters::ColorFilter(color_filter, nullptr);
+        },
+        [&](CSS::ResolvedFilter::HueRotate const& hue_rotate) {
+            float radians = AK::to_radians(hue_rotate.angle_degrees);
+
+            auto cosA = cos(radians);
+            auto sinA = sin(radians);
+
+            auto a00 = 0.213f + cosA * 0.787f - sinA * 0.213f;
+            auto a01 = 0.715f - cosA * 0.715f - sinA * 0.715f;
+            auto a02 = 0.072f - cosA * 0.072f + sinA * 0.928f;
+            auto a10 = 0.213f - cosA * 0.213f + sinA * 0.143f;
+            auto a11 = 0.715f + cosA * 0.285f + sinA * 0.140f;
+            auto a12 = 0.072f - cosA * 0.072f - sinA * 0.283f;
+            auto a20 = 0.213f - cosA * 0.213f - sinA * 0.787f;
+            auto a21 = 0.715f - cosA * 0.715f + sinA * 0.715f;
+            auto a22 = 0.072f + cosA * 0.928f + sinA * 0.072f;
+
+            float matrix[20] = {
+                a00, a01, a02, 0, 0,
+                a10, a11, a12, 0, 0,
+                a20, a21, a22, 0, 0,
+                0, 0, 0, 1, 0
+            };
+
+            auto color_filter = SkColorFilters::Matrix(matrix);
+            return SkImageFilters::ColorFilter(color_filter, nullptr);
+        },
+        [&](CSS::ResolvedFilter::DropShadow const&) {
+            dbgln("TODO: Implement drop-shadow() filter function!");
+            return sk_sp<SkImageFilter> {};
+        });
+}
+
 static SkPath to_skia_path(Gfx::Path const& path)
 {
     return static_cast<Gfx::PathImplSkia const&>(path.impl()).sk_path();
@@ -444,7 +566,33 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
                              .translate(-command.transform.origin);
     auto matrix = to_skia_matrix(new_transform);
 
-    if (command.opacity < 1) {
+    if (!command.filter.is_none()) {
+        sk_sp<SkImageFilter> image_filter;
+        auto append_filter = [&image_filter](auto new_filter) {
+            if (image_filter)
+                image_filter = SkImageFilters::Compose(new_filter, image_filter);
+            else
+                image_filter = new_filter;
+        };
+
+        // Apply filters in order
+        for (auto const& filter_function : command.filter.filters)
+            append_filter(to_skia_image_filter(filter_function));
+
+        // We apply opacity as a color filter here so we only need to save and restore a single layer.
+        if (command.opacity < 1) {
+            append_filter(to_skia_image_filter(CSS::ResolvedFilter::FilterFunction {
+                CSS::ResolvedFilter::Color {
+                    CSS::FilterOperation::Color::Type::Opacity,
+                    command.opacity,
+                },
+            }));
+        }
+
+        SkPaint paint;
+        paint.setImageFilter(image_filter);
+        canvas.saveLayer(nullptr, &paint);
+    } else if (command.opacity < 1) {
         auto source_paintable_rect = to_skia_rect(command.source_paintable_rect);
         SkRect dest;
         matrix.mapRect(&dest, source_paintable_rect);
@@ -453,9 +601,8 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
         canvas.save();
     }
 
-    if (command.clip_path.has_value()) {
+    if (command.clip_path.has_value())
         canvas.clipPath(to_skia_path(command.clip_path.value()), true);
-    }
 
     canvas.concat(matrix);
 }
@@ -962,129 +1109,9 @@ void DisplayListPlayerSkia::apply_backdrop_filter(ApplyBackdropFilter const& com
     ScopeGuard guard = [&] { canvas.restore(); };
 
     for (auto const& filter_function : command.backdrop_filter.filters) {
-        // See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
-        filter_function.visit(
-            [&](CSS::ResolvedFilter::Blur const& blur_filter) {
-                auto blur_image_filter = SkImageFilters::Blur(blur_filter.radius, blur_filter.radius, nullptr);
-                canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, blur_image_filter.get(), 0));
-                canvas.restore();
-            },
-            [&](CSS::ResolvedFilter::Color const& color) {
-                auto amount = clamp(color.amount, 0.0f, 1.0f);
-
-                // Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
-                sk_sp<SkColorFilter> color_filter;
-                switch (color.type) {
-                case CSS::FilterOperation::Color::Type::Grayscale: {
-                    float matrix[20] = {
-                        0.2126f + 0.7874f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
-                        0.2126f - 0.2126f * (1 - amount), 0.7152f + 0.2848f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
-                        0.2126f - 0.2126f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f + 0.9278f * (1 - amount), 0, 0,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Brightness: {
-                    float matrix[20] = {
-                        amount, 0, 0, 0, 0,
-                        0, amount, 0, 0, 0,
-                        0, 0, amount, 0, 0,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Contrast: {
-                    float intercept = -(0.5f * amount) + 0.5f;
-                    float matrix[20] = {
-                        amount, 0, 0, 0, intercept,
-                        0, amount, 0, 0, intercept,
-                        0, 0, amount, 0, intercept,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Invert: {
-                    float matrix[20] = {
-                        1 - 2 * amount, 0, 0, 0, amount,
-                        0, 1 - 2 * amount, 0, 0, amount,
-                        0, 0, 1 - 2 * amount, 0, amount,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Opacity: {
-                    float matrix[20] = {
-                        1, 0, 0, 0, 0,
-                        0, 1, 0, 0, 0,
-                        0, 0, 1, 0, 0,
-                        0, 0, 0, amount, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Sepia: {
-                    float matrix[20] = {
-                        0.393f + 0.607f * (1 - amount), 0.769f - 0.769f * (1 - amount), 0.189f - 0.189f * (1 - amount), 0, 0,
-                        0.349f - 0.349f * (1 - amount), 0.686f + 0.314f * (1 - amount), 0.168f - 0.168f * (1 - amount), 0, 0,
-                        0.272f - 0.272f * (1 - amount), 0.534f - 0.534f * (1 - amount), 0.131f + 0.869f * (1 - amount), 0, 0,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                case CSS::FilterOperation::Color::Type::Saturate: {
-                    float matrix[20] = {
-                        0.213f + 0.787f * amount, 0.715f - 0.715f * amount, 0.072f - 0.072f * amount, 0, 0,
-                        0.213f - 0.213f * amount, 0.715f + 0.285f * amount, 0.072f - 0.072f * amount, 0, 0,
-                        0.213f - 0.213f * amount, 0.715f - 0.715f * amount, 0.072f + 0.928f * amount, 0, 0,
-                        0, 0, 0, 1, 0
-                    };
-                    color_filter = SkColorFilters::Matrix(matrix);
-                    break;
-                }
-                default:
-                    VERIFY_NOT_REACHED();
-                }
-
-                auto image_filter = SkImageFilters::ColorFilter(color_filter, nullptr);
-                canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
-                canvas.restore();
-            },
-            [&](CSS::ResolvedFilter::HueRotate const& hue_rotate) {
-                float radians = AK::to_radians(hue_rotate.angle_degrees);
-
-                auto cosA = cos(radians);
-                auto sinA = sin(radians);
-
-                auto a00 = 0.213f + cosA * 0.787f - sinA * 0.213f;
-                auto a01 = 0.715f - cosA * 0.715f - sinA * 0.715f;
-                auto a02 = 0.072f - cosA * 0.072f + sinA * 0.928f;
-                auto a10 = 0.213f - cosA * 0.213f + sinA * 0.143f;
-                auto a11 = 0.715f + cosA * 0.285f + sinA * 0.140f;
-                auto a12 = 0.072f - cosA * 0.072f - sinA * 0.283f;
-                auto a20 = 0.213f - cosA * 0.213f - sinA * 0.787f;
-                auto a21 = 0.715f - cosA * 0.715f + sinA * 0.715f;
-                auto a22 = 0.072f + cosA * 0.928f + sinA * 0.072f;
-
-                float matrix[20] = {
-                    a00, a01, a02, 0, 0,
-                    a10, a11, a12, 0, 0,
-                    a20, a21, a22, 0, 0,
-                    0, 0, 0, 1, 0
-                };
-
-                auto color_filter = SkColorFilters::Matrix(matrix);
-                auto image_filter = SkImageFilters::ColorFilter(color_filter, nullptr);
-                canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
-                canvas.restore();
-            },
-            [&](CSS::ResolvedFilter::DropShadow const&) {
-                dbgln("TODO: Implement drop-shadow() filter function!");
-            });
+        auto image_filter = to_skia_image_filter(filter_function);
+        canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
+        canvas.restore();
     }
 }
 

+ 1 - 0
Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp

@@ -284,6 +284,7 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params
 {
     append(PushStackingContext {
         .opacity = params.opacity,
+        .filter = params.filter,
         .source_paintable_rect = params.source_paintable_rect,
         .transform = {
             .origin = params.transform.origin,

+ 1 - 0
Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h

@@ -111,6 +111,7 @@ public:
 
     struct PushStackingContextParams {
         float opacity;
+        CSS::ResolvedFilter filter;
         bool is_fixed_position;
         Gfx::IntRect source_paintable_rect;
         StackingContextTransform transform;

+ 1 - 0
Userland/Libraries/LibWeb/Painting/StackingContext.cpp

@@ -311,6 +311,7 @@ void StackingContext::paint(PaintContext& context) const
 
     DisplayListRecorder::PushStackingContextParams push_stacking_context_params {
         .opacity = opacity,
+        .filter = paintable().computed_values().filter(),
         .is_fixed_position = paintable().is_fixed_position(),
         .source_paintable_rect = source_paintable_rect,
         .transform = {