diff --git a/Libraries/LibGfx/Filter.cpp b/Libraries/LibGfx/Filter.cpp index a052fa8b2ad..d01c320efad 100644 --- a/Libraries/LibGfx/Filter.cpp +++ b/Libraries/LibGfx/Filter.cpp @@ -50,17 +50,20 @@ Filter Filter::compose(Filter const& outer, Filter const& inner) return Filter(Impl::create(filter)); } -Filter Filter::blend(Filter const& background, Filter const& foreground, Gfx::CompositingAndBlendingOperator mode) +Filter Filter::blend(Optional background, Optional foreground, Gfx::CompositingAndBlendingOperator mode) { - auto filter = SkImageFilters::Blend(to_skia_blender(mode), background.m_impl->filter, foreground.m_impl->filter); + sk_sp background_skia = background.has_value() ? background->m_impl->filter : nullptr; + sk_sp foreground_skia = foreground.has_value() ? foreground->m_impl->filter : nullptr; + + auto filter = SkImageFilters::Blend(to_skia_blender(mode), background_skia, foreground_skia); return Filter(Impl::create(filter)); } -Filter Filter::blur(float radius, Optional input) +Filter Filter::blur(float radius_x, float radius_y, Optional input) { sk_sp input_skia = input.has_value() ? input->m_impl->filter : nullptr; - auto filter = SkImageFilters::Blur(radius, radius, input_skia); + auto filter = SkImageFilters::Blur(radius_x, radius_y, input_skia); return Filter(Impl::create(filter)); } diff --git a/Libraries/LibGfx/Filter.h b/Libraries/LibGfx/Filter.h index 6395f9d3da7..8c2be5ce07d 100644 --- a/Libraries/LibGfx/Filter.h +++ b/Libraries/LibGfx/Filter.h @@ -32,10 +32,10 @@ public: ~Filter(); static Filter compose(Filter const& outer, Filter const& inner); - static Filter blend(Filter const& background, Filter const& foreground, CompositingAndBlendingOperator mode); + static Filter blend(Optional background, Optional foreground, CompositingAndBlendingOperator mode); static Filter flood(Gfx::Color color, float opacity); static Filter drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color, Optional input = {}); - static Filter blur(float radius, Optional input = {}); + static Filter blur(float radius_x, float radius_y, Optional input = {}); static Filter color(ColorFilterType type, float amount, Optional input = {}); static Filter color_matrix(float matrix[20], Optional input = {}); static Filter saturate(float value, Optional input = {}); diff --git a/Libraries/LibWeb/CSS/Filter.h b/Libraries/LibWeb/CSS/Filter.h index 7badbea8131..72d8e8dbbb9 100644 --- a/Libraries/LibWeb/CSS/Filter.h +++ b/Libraries/LibWeb/CSS/Filter.h @@ -28,7 +28,7 @@ public: bool has_filters() const { return m_filter_value_list; } bool is_none() const { return !has_filters(); } - ReadonlySpan filters() const + ReadonlySpan filters() const { VERIFY(has_filters()); return m_filter_value_list->filter_value_list().span(); diff --git a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp index 5b63b77fee9..eff705585b3 100644 --- a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp @@ -5049,11 +5049,11 @@ RefPtr Parser::parse_filter_value_list_value(TokenStream Optional { + auto parse_filter_function = [&](auto filter_token, auto const& function_values) -> Optional { TokenStream tokens { function_values }; tokens.discard_whitespace(); - auto if_no_more_tokens_return = [&](auto filter) -> Optional { + auto if_no_more_tokens_return = [&](auto filter) -> Optional { tokens.discard_whitespace(); if (tokens.has_next_token()) return {}; @@ -5149,12 +5149,19 @@ RefPtr Parser::parse_filter_value_list_value(TokenStream filter_value_list {}; + Vector filter_value_list {}; while (tokens.has_next_token()) { tokens.discard_whitespace(); if (!tokens.has_next_token()) break; + + auto url_function = parse_url_function(tokens); + if (url_function.has_value()) { + filter_value_list.append(*url_function); + continue; + } + auto& token = tokens.consume_a_token(); if (!token.is_function()) return nullptr; diff --git a/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp index 88ce91e367f..9904752d354 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.cpp @@ -98,6 +98,9 @@ String FilterValueListStyleValue::to_string(SerializationMode) const }()); builder.append(color.amount.to_string()); + }, + [&](CSS::URL const& url) { + builder.append(url.to_string()); }); builder.append(')'); first = false; diff --git a/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h index 559c1e56842..57c75da7403 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FilterValueListStyleValue.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace Web::CSS { @@ -53,18 +54,18 @@ struct Color { }; -using FilterFunction = Variant; +using FilterValue = Variant; class FilterValueListStyleValue final : public StyleValueWithDefaultOperators { public: static ValueComparingNonnullRefPtr create( - Vector filter_value_list) + Vector filter_value_list) { VERIFY(filter_value_list.size() >= 1); return adopt_ref(*new (nothrow) FilterValueListStyleValue(move(filter_value_list))); } - Vector const& filter_value_list() const { return m_filter_value_list; } + Vector const& filter_value_list() const { return m_filter_value_list; } virtual String to_string(SerializationMode) const override; @@ -73,14 +74,14 @@ public: bool properties_equal(FilterValueListStyleValue const& other) const { return m_filter_value_list == other.m_filter_value_list; } private: - FilterValueListStyleValue(Vector filter_value_list) + FilterValueListStyleValue(Vector filter_value_list) : StyleValueWithDefaultOperators(Type::FilterValueList) , m_filter_value_list(move(filter_value_list)) { } // FIXME: No support for SVG filters yet - Vector m_filter_value_list; + Vector m_filter_value_list; }; } diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index e1ebfad40d6..9add19b58fe 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -1049,7 +1049,7 @@ void CanvasRenderingContext2D::set_filter(String filter) item.visit( [&](CSS::FilterOperation::Blur const& blur_filter) { float radius = blur_filter.resolved_radius(*layout_node); - auto new_filter = Gfx::Filter::blur(radius); + auto new_filter = Gfx::Filter::blur(radius, radius); drawing_state().filter = drawing_state().filter.has_value() ? Gfx::Filter::compose(new_filter, *drawing_state().filter) @@ -1093,6 +1093,11 @@ void CanvasRenderingContext2D::set_filter(String filter) drawing_state().filter = drawing_state().filter.has_value() ? Gfx::Filter::compose(new_filter, *drawing_state().filter) : new_filter; + }, + [&](CSS::URL const& url) { + (void)url; + // FIXME: Resolve the SVG filter + dbgln("FIXME: SVG filters are not implemented for Canvas2D"); }); } diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index 28974d3291a..c030fce20fa 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace Web::Layout { @@ -591,7 +592,8 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) for (auto const& filter : computed_filter.filters()) { filter.visit( [&](CSS::FilterOperation::Blur const& blur) { - auto new_filter = Gfx::Filter::blur(blur.resolved_radius(*this)); + auto resolved_radius = blur.resolved_radius(*this); + auto new_filter = Gfx::Filter::blur(resolved_radius, resolved_radius); resolved_filter = resolved_filter.has_value() ? Gfx::Filter::compose(new_filter, *resolved_filter) @@ -627,6 +629,39 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) resolved_filter = resolved_filter.has_value() ? Gfx::Filter::compose(new_filter, *resolved_filter) : new_filter; + }, + [&](CSS::URL const& css_url) { + // FIXME: This is not the right place to resolve SVG filters. Some filter primitives + // wont work if the filter is referenced before its defined because some parameters + // are passed by CSS property. Ideally they should be resolved in another pass or + // lazily. + + auto& url_string = css_url.url(); + + if (url_string.is_empty() || !url_string.starts_with('#')) + return; + + auto fragment_or_error = url_string.substring_from_byte_offset(1); + + if (fragment_or_error.is_error()) + return; + + // FIXME: Support urls that are not only composed of a fragment. + auto maybe_filter = document().get_element_by_id(fragment_or_error.value()); + + if (!maybe_filter) + return; + + if (auto* filter_element = as_if(*maybe_filter)) { + Optional new_filter = filter_element->gfx_filter(); + + if (!new_filter.has_value()) + return; + + resolved_filter = resolved_filter.has_value() + ? Gfx::Filter::compose(*new_filter, *resolved_filter) + : new_filter; + } }); } return resolved_filter; diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.cpp b/Libraries/LibWeb/SVG/SVGFilterElement.cpp index 662d8cb89ce..81bb903cfe6 100644 --- a/Libraries/LibWeb/SVG/SVGFilterElement.cpp +++ b/Libraries/LibWeb/SVG/SVGFilterElement.cpp @@ -1,11 +1,15 @@ /* * Copyright (c) 2025, Jelle Raaijmakers + * Copyright (c) 2025, Lucien Fiorini * * SPDX-License-Identifier: BSD-2-Clause */ #include #include +#include +#include +#include #include namespace Web::SVG { @@ -23,6 +27,12 @@ void SVGFilterElement::initialize(JS::Realm& realm) Base::initialize(realm); } +void SVGFilterElement::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + SVGURIReferenceMixin::visit_edges(visitor); +} + void SVGFilterElement::apply_presentational_hints(GC::Ref cascaded_properties) const { Base::apply_presentational_hints(cascaded_properties); @@ -63,6 +73,82 @@ void SVGFilterElement::attribute_changed(FlyString const& name, Optional m_primitive_units = AttributeParser::parse_units(value.value_or({})); } +Gfx::Filter SVGFilterElement::gfx_filter() +{ + HashMap result_map; + Optional root_filter; + + // https://www.w3.org/TR/filter-effects-1/#element-attrdef-filter-primitive-in + auto resolve_input_filter = [&](String const& name) -> Optional { + // TODO: Add missing ones. + if (name == "SourceGraphic"sv) + return {}; + if (name == "SourceAlpha"sv) { + float matrix[20] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + }; + return Gfx::Filter::color_matrix(matrix); + } + + auto filter_from_map = result_map.get(name).copy(); + + if (filter_from_map.has_value()) + return filter_from_map; + + return root_filter; + }; + + for_each_child([&](auto& node) { + if (is(node)) { + auto& flood_primitive = static_cast(node); + + root_filter = Gfx::Filter::flood(flood_primitive.flood_color(), flood_primitive.flood_opacity()); + + auto result = flood_primitive.result()->base_val(); + if (!result.is_empty()) { + result_map.set(result, *root_filter); + } + } else if (is(node)) { + auto& blend_primitive = static_cast(node); + + auto foreground = resolve_input_filter(blend_primitive.in1()->base_val()); + + auto background = resolve_input_filter(blend_primitive.in2()->base_val()); + + // FIXME: Actually resolve the blend mode + auto blend_mode = Gfx::CompositingAndBlendingOperator::Normal; + + root_filter = Gfx::Filter::blend(background, foreground, blend_mode); + + auto result = blend_primitive.result()->base_val(); + if (!result.is_empty()) { + result_map.set(result, *root_filter); + } + } else if (is(node)) { + auto& blur_primitive = static_cast(node); + + auto input = resolve_input_filter(blur_primitive.in1()->base_val()); + + auto radius_x = blur_primitive.std_deviation_x()->base_val(); + auto radius_y = blur_primitive.std_deviation_y()->base_val(); + + root_filter = Gfx::Filter::blur(radius_x, radius_y, input); + + auto result = blur_primitive.result()->base_val(); + if (!result.is_empty()) { + result_map.set(result, *root_filter); + } + } + + return IterationDecision::Continue; + }); + + return *root_filter; +} + // https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-filterunits GC::Ref SVGFilterElement::filter_units() const { diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.h b/Libraries/LibWeb/SVG/SVGFilterElement.h index dd17c805dae..e6311d2d694 100644 --- a/Libraries/LibWeb/SVG/SVGFilterElement.h +++ b/Libraries/LibWeb/SVG/SVGFilterElement.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -30,6 +31,8 @@ public: virtual void attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) override; + Gfx::Filter gfx_filter(); + GC::Ref filter_units() const; GC::Ref primitive_units() const; GC::Ref x() const; @@ -41,6 +44,7 @@ private: SVGFilterElement(DOM::Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; Optional m_filter_units {}; Optional m_primitive_units {};