mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-19 15:43:20 +00:00
LibWeb: Implement <feComposite>
SVG filter
This commit is contained in:
parent
bbfc3a0f5e
commit
a00e7cb20b
Notes:
github-actions[bot]
2025-09-30 21:34:29 +00:00
Author: https://github.com/tcl3
Commit: a00e7cb20b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5744
Reviewed-by: https://github.com/gmta ✅
Reviewed-by: https://github.com/konradekk
15 changed files with 303 additions and 0 deletions
|
@ -9,6 +9,7 @@
|
|||
#include <LibGfx/SkiaUtils.h>
|
||||
#include <core/SkBlendMode.h>
|
||||
#include <core/SkColorFilter.h>
|
||||
#include <core/SkScalar.h>
|
||||
#include <effects/SkColorMatrix.h>
|
||||
#include <effects/SkImageFilters.h>
|
||||
|
||||
|
@ -41,6 +42,16 @@ FilterImpl const& Filter::impl() const
|
|||
return *m_impl;
|
||||
}
|
||||
|
||||
Filter Filter::arithmetic(Optional<Filter const&> background, Optional<Filter const&> foreground, float k1, float k2, float k3, float k4)
|
||||
{
|
||||
sk_sp<SkImageFilter> background_skia = background.has_value() ? background->m_impl->filter : nullptr;
|
||||
sk_sp<SkImageFilter> foreground_skia = foreground.has_value() ? foreground->m_impl->filter : nullptr;
|
||||
|
||||
auto filter = SkImageFilters::Arithmetic(
|
||||
SkFloatToScalar(k1), SkFloatToScalar(k2), SkFloatToScalar(k3), SkFloatToScalar(k4), false, move(background_skia), move(foreground_skia));
|
||||
return Filter(Impl::create(filter));
|
||||
}
|
||||
|
||||
Filter Filter::compose(Filter const& outer, Filter const& inner)
|
||||
{
|
||||
auto inner_skia = inner.m_impl->filter;
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
|
||||
~Filter();
|
||||
|
||||
static Filter arithmetic(Optional<Filter const&> background, Optional<Filter const&> foreground, float k1, float k2, float k3, float k4);
|
||||
static Filter compose(Filter const& outer, Filter const& inner);
|
||||
static Filter blend(Optional<Filter const&> background, Optional<Filter const&> foreground, CompositingAndBlendingOperator mode);
|
||||
static Filter flood(Gfx::Color color, float opacity);
|
||||
|
|
|
@ -903,6 +903,7 @@ set(SOURCES
|
|||
SVG/SVGElement.cpp
|
||||
SVG/SVGEllipseElement.cpp
|
||||
SVG/SVGFEBlendElement.cpp
|
||||
SVG/SVGFECompositeElement.cpp
|
||||
SVG/SVGFEFloodElement.cpp
|
||||
SVG/SVGFEGaussianBlurElement.cpp
|
||||
SVG/SVGFEImageElement.cpp
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
#include <LibWeb/SVG/SVGDescElement.h>
|
||||
#include <LibWeb/SVG/SVGEllipseElement.h>
|
||||
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||
#include <LibWeb/SVG/SVGFECompositeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
|
@ -471,6 +472,8 @@ static GC::Ref<SVG::SVGElement> create_svg_element(JS::Realm& realm, Document& d
|
|||
return realm.create<SVG::SVGEllipseElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feBlend)
|
||||
return realm.create<SVG::SVGFEBlendElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feComposite)
|
||||
return realm.create<SVG::SVGFECompositeElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feFlood)
|
||||
return realm.create<SVG::SVGFEFloodElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feGaussianBlur)
|
||||
|
|
|
@ -1087,6 +1087,7 @@ class SVGDescElement;
|
|||
class SVGElement;
|
||||
class SVGEllipseElement;
|
||||
class SVGFEBlendElement;
|
||||
class SVGFECompositeElement;
|
||||
class SVGFEFloodElement;
|
||||
class SVGFEGaussianBlurElement;
|
||||
class SVGFEImageElement;
|
||||
|
|
|
@ -39,6 +39,10 @@ namespace Web::SVG::AttributeNames {
|
|||
__ENUMERATE_SVG_ATTRIBUTE(in2, "in2") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(kernelMatrix, "kernelMatrix") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(kernelUnitLength, "kernelUnitLength") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(k1, "k1") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(k2, "k2") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(k3, "k3") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(k4, "k4") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(keyPoints, "keyPoints") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(keySplines, "keySplines") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(keyTimes, "keyTimes") \
|
||||
|
@ -53,6 +57,7 @@ namespace Web::SVG::AttributeNames {
|
|||
__ENUMERATE_SVG_ATTRIBUTE(numOctaves, "numOctaves") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(offset, "offset") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(opacity, "opacity") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(operator_, "operator") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(pathLength, "pathLength") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(patternContentUnits, "patternContentUnits") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(patternTransform, "patternTransform") \
|
||||
|
|
133
Libraries/LibWeb/SVG/SVGFECompositeElement.cpp
Normal file
133
Libraries/LibWeb/SVG/SVGFECompositeElement.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/SVGFECompositeElementPrototype.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
|
||||
#include <LibWeb/SVG/SVGFECompositeElement.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(SVGFECompositeElement);
|
||||
|
||||
SVGFECompositeElement::SVGFECompositeElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: SVGElement(document, qualified_name)
|
||||
{
|
||||
}
|
||||
|
||||
void SVGFECompositeElement::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFECompositeElement);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void SVGFECompositeElement::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
SVGFilterPrimitiveStandardAttributes::visit_edges(visitor);
|
||||
visitor.visit(m_in1);
|
||||
visitor.visit(m_in2);
|
||||
visitor.visit(m_k1);
|
||||
visitor.visit(m_k2);
|
||||
visitor.visit(m_k3);
|
||||
visitor.visit(m_k4);
|
||||
}
|
||||
|
||||
static SVGFECompositeElement::CompositingOperator string_to_compositing_operator(StringView string)
|
||||
{
|
||||
if (string == "over"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::Over;
|
||||
if (string == "in"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::In;
|
||||
if (string == "out"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::Out;
|
||||
if (string == "atop"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::Atop;
|
||||
if (string == "xor"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::Xor;
|
||||
if (string == "arithmetic"sv)
|
||||
return SVGFECompositeElement::CompositingOperator::Arithmetic;
|
||||
return SVGFECompositeElement::CompositingOperator::Unknown;
|
||||
}
|
||||
|
||||
void SVGFECompositeElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& new_value, Optional<FlyString> const& namespace_)
|
||||
{
|
||||
Base::attribute_changed(name, old_value, new_value, namespace_);
|
||||
|
||||
if (name == SVG::AttributeNames::operator_) {
|
||||
auto parse_compositing_operator = [](Optional<String> const& value) -> Optional<CompositingOperator> {
|
||||
if (!value.has_value())
|
||||
return {};
|
||||
return string_to_compositing_operator(*value);
|
||||
};
|
||||
|
||||
m_operator = parse_compositing_operator(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedString> SVGFECompositeElement::in1()
|
||||
{
|
||||
if (!m_in1)
|
||||
m_in1 = SVGAnimatedString::create(realm(), *this, AttributeNames::in);
|
||||
|
||||
return *m_in1;
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedString> SVGFECompositeElement::in2()
|
||||
{
|
||||
if (!m_in2)
|
||||
m_in2 = SVGAnimatedString::create(realm(), *this, AttributeNames::in2);
|
||||
|
||||
return *m_in2;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomposite-k1
|
||||
GC::Ref<SVGAnimatedNumber> SVGFECompositeElement::k1()
|
||||
{
|
||||
if (!m_k1)
|
||||
m_k1 = SVGAnimatedNumber::create(realm(), *this, AttributeNames::k1, 0.f);
|
||||
|
||||
return *m_k1;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomposite-k2
|
||||
GC::Ref<SVGAnimatedNumber> SVGFECompositeElement::k2()
|
||||
{
|
||||
if (!m_k2)
|
||||
m_k2 = SVGAnimatedNumber::create(realm(), *this, AttributeNames::k2, 0.f);
|
||||
|
||||
return *m_k2;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomposite-k3
|
||||
GC::Ref<SVGAnimatedNumber> SVGFECompositeElement::k3()
|
||||
{
|
||||
if (!m_k3)
|
||||
m_k3 = SVGAnimatedNumber::create(realm(), *this, AttributeNames::k3, 0.f);
|
||||
|
||||
return *m_k3;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomposite-k4
|
||||
GC::Ref<SVGAnimatedNumber> SVGFECompositeElement::k4()
|
||||
{
|
||||
if (!m_k4)
|
||||
m_k4 = SVGAnimatedNumber::create(realm(), *this, AttributeNames::k4, 0.f);
|
||||
|
||||
return *m_k4;
|
||||
}
|
||||
|
||||
SVGFECompositeElement::CompositingOperator SVGFECompositeElement::operator_() const
|
||||
{
|
||||
return m_operator.value_or(CompositingOperator::Over);
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomposite-operator
|
||||
GC::Ref<SVGAnimatedEnumeration> SVGFECompositeElement::operator_for_bindings() const
|
||||
{
|
||||
return SVGAnimatedEnumeration::create(realm(), to_underlying(m_operator.value_or(CompositingOperator::Over)));
|
||||
}
|
||||
|
||||
}
|
66
Libraries/LibWeb/SVG/SVGFECompositeElement.h
Normal file
66
Libraries/LibWeb/SVG/SVGFECompositeElement.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SVGAnimatedNumber.h"
|
||||
|
||||
#include <LibWeb/SVG/SVGAnimatedLength.h>
|
||||
#include <LibWeb/SVG/SVGElement.h>
|
||||
#include <LibWeb/SVG/SVGFilterPrimitiveStandardAttributes.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
class SVGFECompositeElement final
|
||||
: public SVGElement
|
||||
, public SVGFilterPrimitiveStandardAttributes<SVGFECompositeElement> {
|
||||
WEB_PLATFORM_OBJECT(SVGFECompositeElement, SVGElement);
|
||||
GC_DECLARE_ALLOCATOR(SVGFECompositeElement);
|
||||
|
||||
public:
|
||||
virtual ~SVGFECompositeElement() override = default;
|
||||
|
||||
GC::Ref<SVGAnimatedString> in1();
|
||||
GC::Ref<SVGAnimatedString> in2();
|
||||
|
||||
GC::Ref<SVGAnimatedNumber> k1();
|
||||
GC::Ref<SVGAnimatedNumber> k2();
|
||||
GC::Ref<SVGAnimatedNumber> k3();
|
||||
GC::Ref<SVGAnimatedNumber> k4();
|
||||
|
||||
enum class CompositingOperator : u8 {
|
||||
Unknown = 0,
|
||||
Over = 1,
|
||||
In = 2,
|
||||
Out = 3,
|
||||
Atop = 4,
|
||||
Xor = 5,
|
||||
Arithmetic = 6,
|
||||
Lighter = 7,
|
||||
};
|
||||
SVGFECompositeElement::CompositingOperator operator_() const;
|
||||
GC::Ref<SVGAnimatedEnumeration> operator_for_bindings() const;
|
||||
|
||||
private:
|
||||
SVGFECompositeElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& new_value, Optional<FlyString> const& namespace_) override;
|
||||
|
||||
GC::Ptr<SVGAnimatedString> m_in1;
|
||||
GC::Ptr<SVGAnimatedString> m_in2;
|
||||
|
||||
GC::Ptr<SVGAnimatedNumber> m_k1;
|
||||
GC::Ptr<SVGAnimatedNumber> m_k2;
|
||||
GC::Ptr<SVGAnimatedNumber> m_k3;
|
||||
GC::Ptr<SVGAnimatedNumber> m_k4;
|
||||
|
||||
Optional<CompositingOperator> m_operator;
|
||||
};
|
||||
|
||||
}
|
26
Libraries/LibWeb/SVG/SVGFECompositeElement.idl
Normal file
26
Libraries/LibWeb/SVG/SVGFECompositeElement.idl
Normal file
|
@ -0,0 +1,26 @@
|
|||
#import <SVG/SVGAnimatedEnumeration.idl>
|
||||
#import <SVG/SVGAnimatedString.idl>
|
||||
#import <SVG/SVGFilterPrimitiveStandardAttributes.idl>
|
||||
|
||||
// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFECompositeElement
|
||||
[Exposed=Window]
|
||||
interface SVGFECompositeElement : SVGElement {
|
||||
// Composite Operators
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_UNKNOWN = 0;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_OVER = 1;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_IN = 2;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_OUT = 3;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_ATOP = 4;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_XOR = 5;
|
||||
const unsigned short SVG_FECOMPOSITE_OPERATOR_ARITHMETIC = 6;
|
||||
|
||||
readonly attribute SVGAnimatedString in1;
|
||||
readonly attribute SVGAnimatedString in2;
|
||||
[ImplementedAs=operator_for_bindings] readonly attribute SVGAnimatedEnumeration operator;
|
||||
readonly attribute SVGAnimatedNumber k2;
|
||||
readonly attribute SVGAnimatedNumber k3;
|
||||
readonly attribute SVGAnimatedNumber k1;
|
||||
readonly attribute SVGAnimatedNumber k4;
|
||||
};
|
||||
|
||||
SVGFECompositeElement includes SVGFilterPrimitiveStandardAttributes;
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||
#include <LibWeb/SVG/SVGFECompositeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
|
@ -125,6 +126,42 @@ Optional<Gfx::Filter> SVGFilterElement::gfx_filter(Layout::NodeWithStyle const&
|
|||
|
||||
root_filter = Gfx::Filter::blend(background, foreground, blend_mode);
|
||||
update_result_map(*blend_primitive);
|
||||
} else if (auto* composite_primitive = as_if<SVGFECompositeElement>(node)) {
|
||||
auto foreground = resolve_input_filter(composite_primitive->in1()->base_val());
|
||||
auto background = resolve_input_filter(composite_primitive->in2()->base_val());
|
||||
auto operator_ = composite_primitive->operator_();
|
||||
if (operator_ == SVGFECompositeElement::CompositingOperator::Arithmetic) {
|
||||
auto k1 = composite_primitive->k1()->base_val();
|
||||
auto k2 = composite_primitive->k2()->base_val();
|
||||
auto k3 = composite_primitive->k3()->base_val();
|
||||
auto k4 = composite_primitive->k4()->base_val();
|
||||
|
||||
root_filter = Gfx::Filter::arithmetic(background, foreground, k1, k2, k3, k4);
|
||||
} else {
|
||||
auto to_compositing_and_blending_operator = [](SVGFECompositeElement::CompositingOperator operator_) {
|
||||
switch (operator_) {
|
||||
case SVGFECompositeElement::CompositingOperator::Over:
|
||||
return Gfx::CompositingAndBlendingOperator::SourceOver;
|
||||
case SVGFECompositeElement::CompositingOperator::In:
|
||||
return Gfx::CompositingAndBlendingOperator::SourceIn;
|
||||
case SVGFECompositeElement::CompositingOperator::Out:
|
||||
return Gfx::CompositingAndBlendingOperator::DestinationOut;
|
||||
case SVGFECompositeElement::CompositingOperator::Atop:
|
||||
return Gfx::CompositingAndBlendingOperator::SourceATop;
|
||||
case SVGFECompositeElement::CompositingOperator::Xor:
|
||||
return Gfx::CompositingAndBlendingOperator::Xor;
|
||||
case SVGFECompositeElement::CompositingOperator::Lighter:
|
||||
return Gfx::CompositingAndBlendingOperator::Lighter;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Gfx::CompositingAndBlendingOperator::SourceOver;
|
||||
};
|
||||
|
||||
root_filter = Gfx::Filter::blend(background, foreground, to_compositing_and_blending_operator(operator_));
|
||||
}
|
||||
|
||||
update_result_map(*composite_primitive);
|
||||
} else if (auto* blur_primitive = as_if<SVGFEGaussianBlurElement>(node)) {
|
||||
auto input = resolve_input_filter(blur_primitive->in1()->base_val());
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Web::SVG::TagNames {
|
|||
__ENUMERATE_SVG_TAG(desc) \
|
||||
__ENUMERATE_SVG_TAG(ellipse) \
|
||||
__ENUMERATE_SVG_TAG(feBlend) \
|
||||
__ENUMERATE_SVG_TAG(feComposite) \
|
||||
__ENUMERATE_SVG_TAG(feFlood) \
|
||||
__ENUMERATE_SVG_TAG(feGaussianBlur) \
|
||||
__ENUMERATE_SVG_TAG(feImage) \
|
||||
|
|
|
@ -388,6 +388,7 @@ libweb_js_bindings(SVG/SVGImageElement)
|
|||
libweb_js_bindings(SVG/SVGCircleElement)
|
||||
libweb_js_bindings(SVG/SVGEllipseElement)
|
||||
libweb_js_bindings(SVG/SVGFEBlendElement)
|
||||
libweb_js_bindings(SVG/SVGFECompositeElement)
|
||||
libweb_js_bindings(SVG/SVGFEFloodElement)
|
||||
libweb_js_bindings(SVG/SVGFEGaussianBlurElement)
|
||||
libweb_js_bindings(SVG/SVGFEImageElement)
|
||||
|
|
4
Tests/LibWeb/Ref/expected/svg/composite-filter-ref.html
Normal file
4
Tests/LibWeb/Ref/expected/svg/composite-filter-ref.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<svg width="110" height="110" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="100" height="100" fill="green" />
|
||||
</svg>
|
12
Tests/LibWeb/Ref/input/svg/composite-filter.html
Normal file
12
Tests/LibWeb/Ref/input/svg/composite-filter.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="../../expected/svg/composite-filter-ref.html" />
|
||||
<svg width="110" height="110" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<filter id="compositeFilter">
|
||||
<feFlood flood-color="green" result="greenFill" />
|
||||
<feComposite in="greenFill" in2="SourceGraphic" operator="in" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect x="10" y="10" width="100" height="100" fill="red" filter="url(#compositeFilter)" />
|
||||
</svg>
|
|
@ -376,6 +376,7 @@ SVGDescElement
|
|||
SVGElement
|
||||
SVGEllipseElement
|
||||
SVGFEBlendElement
|
||||
SVGFECompositeElement
|
||||
SVGFEFloodElement
|
||||
SVGFEGaussianBlurElement
|
||||
SVGFEImageElement
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue