diff --git a/Libraries/LibGfx/Filter.cpp b/Libraries/LibGfx/Filter.cpp index a3c6ba9fe1..e3b4ca2f90 100644 --- a/Libraries/LibGfx/Filter.cpp +++ b/Libraries/LibGfx/Filter.cpp @@ -224,6 +224,16 @@ Filter Filter::hue_rotate(float angle_degrees, Optional input) return Filter(Impl::create(SkImageFilters::ColorFilter(color_filter, input_skia))); } +Filter Filter::merge(Vector> const& inputs) +{ + Vector> skia_filters; + skia_filters.ensure_capacity(inputs.size()); + for (auto& filter : inputs) + skia_filters.unchecked_append(filter.has_value() ? filter->m_impl->filter : nullptr); + + return Filter(Impl::create(SkImageFilters::Merge(skia_filters.data(), skia_filters.size()))); +} + Filter Filter::offset(float dx, float dy, Optional input) { sk_sp input_skia = input.has_value() ? input->m_impl->filter : nullptr; diff --git a/Libraries/LibGfx/Filter.h b/Libraries/LibGfx/Filter.h index c072f4a557..3908fcd684 100644 --- a/Libraries/LibGfx/Filter.h +++ b/Libraries/LibGfx/Filter.h @@ -40,6 +40,7 @@ public: static Filter color_matrix(float matrix[20], Optional input = {}); static Filter saturate(float value, Optional input = {}); static Filter hue_rotate(float angle_degrees, Optional input = {}); + static Filter merge(Vector> const&); static Filter offset(float dx, float dy, Optional input = {}); FilterImpl const& impl() const; diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index d2f51fbc1f..9b28ba6cf0 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -850,6 +850,8 @@ set(SOURCES SVG/SVGFEBlendElement.cpp SVG/SVGFEFloodElement.cpp SVG/SVGFEGaussianBlurElement.cpp + SVG/SVGFEMergeElement.cpp + SVG/SVGFEMergeNodeElement.cpp SVG/SVGFEOffsetElement.cpp SVG/SVGFilterElement.cpp SVG/SVGForeignObjectElement.cpp diff --git a/Libraries/LibWeb/DOM/ElementFactory.cpp b/Libraries/LibWeb/DOM/ElementFactory.cpp index 9203f7f105..17fbb2b36b 100644 --- a/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -94,6 +94,8 @@ #include #include #include +#include +#include #include #include #include @@ -472,6 +474,10 @@ static GC::Ref create_svg_element(JS::Realm& realm, Document& d return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::feGaussianBlur) return realm.create(document, move(qualified_name)); + if (local_name == SVG::TagNames::feMerge) + return realm.create(document, move(qualified_name)); + if (local_name == SVG::TagNames::feMergeNode) + return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::feOffset) return realm.create(document, move(qualified_name)); if (local_name == SVG::TagNames::filter) diff --git a/Libraries/LibWeb/SVG/SVGFEMergeElement.cpp b/Libraries/LibWeb/SVG/SVGFEMergeElement.cpp new file mode 100644 index 0000000000..16091e0780 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeElement.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::SVG { + +GC_DEFINE_ALLOCATOR(SVGFEMergeElement); + +SVGFEMergeElement::SVGFEMergeElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGElement(document, qualified_name) +{ +} + +void SVGFEMergeElement::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEMergeElement); + Base::initialize(realm); +} + +void SVGFEMergeElement::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + SVGFilterPrimitiveStandardAttributes::visit_edges(visitor); +} + +} diff --git a/Libraries/LibWeb/SVG/SVGFEMergeElement.h b/Libraries/LibWeb/SVG/SVGFEMergeElement.h new file mode 100644 index 0000000000..d8ce70de15 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeElement.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::SVG { + +// https://www.w3.org/TR/filter-effects-1/#svgfemergeelement +class SVGFEMergeElement final + : public SVGElement + , public SVGFilterPrimitiveStandardAttributes { + WEB_PLATFORM_OBJECT(SVGFEMergeElement, SVGElement); + GC_DECLARE_ALLOCATOR(SVGFEMergeElement); + +public: + virtual ~SVGFEMergeElement() override = default; + +private: + SVGFEMergeElement(DOM::Document&, DOM::QualifiedName); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; +}; + +} diff --git a/Libraries/LibWeb/SVG/SVGFEMergeElement.idl b/Libraries/LibWeb/SVG/SVGFEMergeElement.idl new file mode 100644 index 0000000000..34e673c50f --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeElement.idl @@ -0,0 +1,9 @@ +#import +#import + +// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMergeElement +[Exposed=Window] +interface SVGFEMergeElement : SVGElement { +}; + +SVGFEMergeElement includes SVGFilterPrimitiveStandardAttributes; diff --git a/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.cpp b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.cpp new file mode 100644 index 0000000000..92d92f1d55 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::SVG { + +GC_DEFINE_ALLOCATOR(SVGFEMergeNodeElement); + +SVGFEMergeNodeElement::SVGFEMergeNodeElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGElement(document, qualified_name) +{ +} + +void SVGFEMergeNodeElement::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEMergeNodeElement); + Base::initialize(realm); +} + +void SVGFEMergeNodeElement::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_in1); +} + +GC::Ref SVGFEMergeNodeElement::in1() +{ + if (!m_in1) + m_in1 = SVGAnimatedString::create(realm(), *this, AttributeNames::in); + + return *m_in1; +} + +} diff --git a/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.h b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.h new file mode 100644 index 0000000000..d90de61c37 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::SVG { + +// https://www.w3.org/TR/filter-effects-1/#svgfemergenodeelement +class SVGFEMergeNodeElement final : public SVGElement { + WEB_PLATFORM_OBJECT(SVGFEMergeNodeElement, SVGElement); + GC_DECLARE_ALLOCATOR(SVGFEMergeNodeElement); + +public: + virtual ~SVGFEMergeNodeElement() override = default; + + GC::Ref in1(); + +private: + SVGFEMergeNodeElement(DOM::Document&, DOM::QualifiedName); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ptr m_in1; +}; + +} diff --git a/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.idl b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.idl new file mode 100644 index 0000000000..e82d0460c6 --- /dev/null +++ b/Libraries/LibWeb/SVG/SVGFEMergeNodeElement.idl @@ -0,0 +1,8 @@ +#import +#import + +// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMergeNodeElement +[Exposed=Window] +interface SVGFEMergeNodeElement : SVGElement { + readonly attribute SVGAnimatedString in1; +}; diff --git a/Libraries/LibWeb/SVG/SVGFilterElement.cpp b/Libraries/LibWeb/SVG/SVGFilterElement.cpp index 6974c4ca86..6dbe849de1 100644 --- a/Libraries/LibWeb/SVG/SVGFilterElement.cpp +++ b/Libraries/LibWeb/SVG/SVGFilterElement.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -130,6 +132,15 @@ Optional SVGFilterElement::gfx_filter() root_filter = Gfx::Filter::blur(radius_x, radius_y, input); update_result_map(*blur_primitive); + } else if (auto* merge_primitive = as_if(node)) { + Vector> merge_inputs; + merge_primitive->template for_each_child_of_type([&](auto& merge_node) { + merge_inputs.append(resolve_input_filter(merge_node.in1()->base_val())); + return IterationDecision::Continue; + }); + + root_filter = Gfx::Filter::merge(merge_inputs); + update_result_map(*merge_primitive); } else if (auto* offset_primitive = as_if(node)) { auto input = resolve_input_filter(offset_primitive->in1()->base_val()); diff --git a/Libraries/LibWeb/SVG/TagNames.h b/Libraries/LibWeb/SVG/TagNames.h index 55577869f3..61ee00c955 100644 --- a/Libraries/LibWeb/SVG/TagNames.h +++ b/Libraries/LibWeb/SVG/TagNames.h @@ -20,6 +20,8 @@ namespace Web::SVG::TagNames { __ENUMERATE_SVG_TAG(feBlend) \ __ENUMERATE_SVG_TAG(feFlood) \ __ENUMERATE_SVG_TAG(feGaussianBlur) \ + __ENUMERATE_SVG_TAG(feMerge) \ + __ENUMERATE_SVG_TAG(feMergeNode) \ __ENUMERATE_SVG_TAG(feOffset) \ __ENUMERATE_SVG_TAG(filter) \ __ENUMERATE_SVG_TAG(foreignObject) \ diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 1e6ee32ed4..2d8d8216bc 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -347,6 +347,8 @@ libweb_js_bindings(SVG/SVGEllipseElement) libweb_js_bindings(SVG/SVGFEBlendElement) libweb_js_bindings(SVG/SVGFEFloodElement) libweb_js_bindings(SVG/SVGFEGaussianBlurElement) +libweb_js_bindings(SVG/SVGFEMergeElement) +libweb_js_bindings(SVG/SVGFEMergeNodeElement) libweb_js_bindings(SVG/SVGFEOffsetElement) libweb_js_bindings(SVG/SVGFilterElement) libweb_js_bindings(SVG/SVGForeignObjectElement) diff --git a/Tests/LibWeb/Ref/expected/svg/offset-merge-filters-ref.html b/Tests/LibWeb/Ref/expected/svg/offset-merge-filters-ref.html new file mode 100644 index 0000000000..68d2925451 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/svg/offset-merge-filters-ref.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Ref/input/svg/offset-merge-filters.html b/Tests/LibWeb/Ref/input/svg/offset-merge-filters.html new file mode 100644 index 0000000000..13da153811 --- /dev/null +++ b/Tests/LibWeb/Ref/input/svg/offset-merge-filters.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 5dd2db43d9..675cfefea3 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -344,6 +344,8 @@ SVGEllipseElement SVGFEBlendElement SVGFEFloodElement SVGFEGaussianBlurElement +SVGFEMergeElement +SVGFEMergeNodeElement SVGFEOffsetElement SVGFilterElement SVGForeignObjectElement