mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-05 01:10:24 +00:00
LibWeb+LibGfx: Implement SVGFEMorphologyElement
This filter primitive is used to erode or dilate an image.
This commit is contained in:
committed by
Andreas Kling
parent
837d5fb7ea
commit
36c6079dbc
Notes:
github-actions[bot]
2025-11-15 15:09:46 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/36c6079dbc6 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6831
@@ -277,6 +277,18 @@ Filter Filter::merge(Vector<Optional<Filter>> const& inputs)
|
||||
return Filter(Impl::create(SkImageFilters::Merge(skia_filters.data(), skia_filters.size())));
|
||||
}
|
||||
|
||||
Filter Filter::erode(float radius_x, float radius_y, Optional<Filter> const& input)
|
||||
{
|
||||
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
|
||||
return Filter(Impl::create(SkImageFilters::Erode(radius_x, radius_y, input_skia)));
|
||||
}
|
||||
|
||||
Filter Filter::dilate(float radius_x, float radius_y, Optional<Filter> const& input)
|
||||
{
|
||||
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
|
||||
return Filter(Impl::create(SkImageFilters::Dilate(radius_x, radius_y, input_skia)));
|
||||
}
|
||||
|
||||
Filter Filter::offset(float dx, float dy, Optional<Filter const&> input)
|
||||
{
|
||||
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
static Filter image(Gfx::ImmutableBitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::IntRect const& dest_rect, Gfx::ScalingMode scaling_mode);
|
||||
static Filter merge(Vector<Optional<Filter>> const&);
|
||||
static Filter offset(float dx, float dy, Optional<Filter const&> input = {});
|
||||
static Filter erode(float radius_x, float radius_y, Optional<Filter> const& input = {});
|
||||
static Filter dilate(float radius_x, float radius_y, Optional<Filter> const& input = {});
|
||||
|
||||
FilterImpl const& impl() const;
|
||||
|
||||
|
||||
17
Libraries/LibGfx/MorphologyOperator.h
Normal file
17
Libraries/LibGfx/MorphologyOperator.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class MorphologyOperator {
|
||||
Unknown,
|
||||
Erode,
|
||||
Dilate,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -927,6 +927,7 @@ set(SOURCES
|
||||
SVG/SVGFEImageElement.cpp
|
||||
SVG/SVGFEMergeElement.cpp
|
||||
SVG/SVGFEMergeNodeElement.cpp
|
||||
SVG/SVGFEMorphologyElement.cpp
|
||||
SVG/SVGFEOffsetElement.cpp
|
||||
SVG/SVGFilterElement.cpp
|
||||
SVG/SVGFilterPrimitiveStandardAttributes.cpp
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeNodeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMorphologyElement.h>
|
||||
#include <LibWeb/SVG/SVGFEOffsetElement.h>
|
||||
#include <LibWeb/SVG/SVGFilterElement.h>
|
||||
#include <LibWeb/SVG/SVGForeignObjectElement.h>
|
||||
@@ -504,6 +505,8 @@ static GC::Ref<SVG::SVGElement> create_svg_element(JS::Realm& realm, Document& d
|
||||
return realm.create<SVG::SVGFEMergeElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feMergeNode)
|
||||
return realm.create<SVG::SVGFEMergeNodeElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feMorphology)
|
||||
return realm.create<SVG::SVGFEMorphologyElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feOffset)
|
||||
return realm.create<SVG::SVGFEOffsetElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::filter)
|
||||
|
||||
@@ -1116,6 +1116,7 @@ class SVGFEFuncGElement;
|
||||
class SVGFEFuncRElement;
|
||||
class SVGFEGaussianBlurElement;
|
||||
class SVGFEImageElement;
|
||||
class SVGFEMorphologyElement;
|
||||
class SVGFilterElement;
|
||||
class SVGFitToViewBox;
|
||||
class SVGForeignObjectElement;
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Web::SVG::AttributeNames {
|
||||
__ENUMERATE_SVG_ATTRIBUTE(preserveAspectRatio, "preserveAspectRatio") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(primitiveUnits, "primitiveUnits") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(r, "r") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(radius, "radius") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(refX, "refX") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(refY, "refY") \
|
||||
__ENUMERATE_SVG_ATTRIBUTE(repeatCount, "repeatCount") \
|
||||
|
||||
85
Libraries/LibWeb/SVG/SVGFEMorphologyElement.cpp
Normal file
85
Libraries/LibWeb/SVG/SVGFEMorphologyElement.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/SVGFEMorphologyElementPrototype.h>
|
||||
#include <LibWeb/SVG/AttributeNames.h>
|
||||
#include <LibWeb/SVG/SVGFEMorphologyElement.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(SVGFEMorphologyElement);
|
||||
|
||||
SVGFEMorphologyElement::SVGFEMorphologyElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: SVGElement(document, qualified_name)
|
||||
{
|
||||
}
|
||||
|
||||
void SVGFEMorphologyElement::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEMorphologyElement);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void SVGFEMorphologyElement::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
SVGFilterPrimitiveStandardAttributes::visit_edges(visitor);
|
||||
visitor.visit(m_in1);
|
||||
visitor.visit(m_radius_x);
|
||||
visitor.visit(m_radius_y);
|
||||
}
|
||||
|
||||
void SVGFEMorphologyElement::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_) {
|
||||
if (!new_value.has_value()) {
|
||||
m_morphology_operator = Gfx::MorphologyOperator::Dilate;
|
||||
return;
|
||||
}
|
||||
if (new_value->equals_ignoring_ascii_case("erode"sv)) {
|
||||
m_morphology_operator = Gfx::MorphologyOperator::Erode;
|
||||
} else if (new_value->equals_ignoring_ascii_case("dilate"sv)) {
|
||||
m_morphology_operator = Gfx::MorphologyOperator::Dilate;
|
||||
} else {
|
||||
m_morphology_operator = Gfx::MorphologyOperator::Dilate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedString> SVGFEMorphologyElement::in1()
|
||||
{
|
||||
if (!m_in1)
|
||||
m_in1 = SVGAnimatedString::create(realm(), *this, DOM::QualifiedName { AttributeNames::in, OptionalNone {}, OptionalNone {} });
|
||||
|
||||
return *m_in1;
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedEnumeration> SVGFEMorphologyElement::operator_for_bindings()
|
||||
{
|
||||
return SVGAnimatedEnumeration::create(realm(), to_underlying(m_morphology_operator));
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedNumber> SVGFEMorphologyElement::radius_x()
|
||||
{
|
||||
if (!m_radius_x)
|
||||
m_radius_x = SVGAnimatedNumber::create(realm(), *this, DOM::QualifiedName { SVG::AttributeNames::radius, OptionalNone {}, OptionalNone {} }, 0.0,
|
||||
SVGAnimatedNumber::SupportsSecondValue::Yes, SVGAnimatedNumber::ValueRepresented::First);
|
||||
|
||||
return *m_radius_x;
|
||||
}
|
||||
|
||||
GC::Ref<SVGAnimatedNumber> SVGFEMorphologyElement::radius_y()
|
||||
{
|
||||
if (!m_radius_y)
|
||||
m_radius_y = SVGAnimatedNumber::create(realm(), *this, DOM::QualifiedName { SVG::AttributeNames::radius, OptionalNone {}, OptionalNone {} }, 0.0,
|
||||
SVGAnimatedNumber::SupportsSecondValue::Yes, SVGAnimatedNumber::ValueRepresented::Second);
|
||||
|
||||
return *m_radius_y;
|
||||
}
|
||||
|
||||
}
|
||||
50
Libraries/LibWeb/SVG/SVGFEMorphologyElement.h
Normal file
50
Libraries/LibWeb/SVG/SVGFEMorphologyElement.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/MorphologyOperator.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedNumber.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedString.h>
|
||||
#include <LibWeb/SVG/SVGElement.h>
|
||||
#include <LibWeb/SVG/SVGFilterPrimitiveStandardAttributes.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
// https://www.w3.org/TR/filter-effects-1/#svgfemorphologyelement
|
||||
class SVGFEMorphologyElement final
|
||||
: public SVGElement
|
||||
, public SVGFilterPrimitiveStandardAttributes<SVGFEMorphologyElement> {
|
||||
WEB_PLATFORM_OBJECT(SVGFEMorphologyElement, SVGElement);
|
||||
GC_DECLARE_ALLOCATOR(SVGFEMorphologyElement);
|
||||
|
||||
public:
|
||||
virtual ~SVGFEMorphologyElement() override = default;
|
||||
|
||||
GC::Ref<SVGAnimatedString> in1();
|
||||
|
||||
GC::Ref<SVGAnimatedEnumeration> operator_for_bindings();
|
||||
Gfx::MorphologyOperator morphology_operator() { return m_morphology_operator; }
|
||||
|
||||
GC::Ref<SVGAnimatedNumber> radius_x();
|
||||
GC::Ref<SVGAnimatedNumber> radius_y();
|
||||
|
||||
private:
|
||||
SVGFEMorphologyElement(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;
|
||||
Gfx::MorphologyOperator m_morphology_operator { Gfx::MorphologyOperator::Erode };
|
||||
GC::Ptr<SVGAnimatedNumber> m_radius_x;
|
||||
GC::Ptr<SVGAnimatedNumber> m_radius_y;
|
||||
};
|
||||
|
||||
}
|
||||
22
Libraries/LibWeb/SVG/SVGFEMorphologyElement.idl
Normal file
22
Libraries/LibWeb/SVG/SVGFEMorphologyElement.idl
Normal file
@@ -0,0 +1,22 @@
|
||||
#import <SVG/SVGAnimatedEnumeration.idl>
|
||||
#import <SVG/SVGAnimatedNumber.idl>
|
||||
#import <SVG/SVGAnimatedString.idl>
|
||||
#import <SVG/SVGElement.idl>
|
||||
#import <SVG/SVGFilterPrimitiveStandardAttributes.idl>
|
||||
|
||||
// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
|
||||
[Exposed=Window]
|
||||
interface SVGFEMorphologyElement : SVGElement {
|
||||
|
||||
// Morphology Operators
|
||||
const unsigned short SVG_MORPHOLOGY_OPERATOR_UNKNOWN = 0;
|
||||
const unsigned short SVG_MORPHOLOGY_OPERATOR_ERODE = 1;
|
||||
const unsigned short SVG_MORPHOLOGY_OPERATOR_DILATE = 2;
|
||||
|
||||
readonly attribute SVGAnimatedString in1;
|
||||
[ImplementedAs=operator_for_bindings] readonly attribute SVGAnimatedEnumeration operator;
|
||||
readonly attribute SVGAnimatedNumber radiusX;
|
||||
readonly attribute SVGAnimatedNumber radiusY;
|
||||
};
|
||||
|
||||
SVGFEMorphologyElement includes SVGFilterPrimitiveStandardAttributes;
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeNodeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMorphologyElement.h>
|
||||
#include <LibWeb/SVG/SVGFEOffsetElement.h>
|
||||
#include <LibWeb/SVG/SVGFilterElement.h>
|
||||
|
||||
@@ -284,6 +285,24 @@ Optional<Gfx::Filter> SVGFilterElement::gfx_filter(Layout::NodeWithStyle const&
|
||||
|
||||
root_filter = Gfx::Filter::merge(merge_inputs);
|
||||
update_result_map(*merge_primitive);
|
||||
} else if (auto* morphology_primitive = as_if<SVGFEMorphologyElement>(node)) {
|
||||
auto input = resolve_input_filter(morphology_primitive->in1()->base_val());
|
||||
|
||||
auto radius_x = morphology_primitive->radius_x()->base_val();
|
||||
auto radius_y = morphology_primitive->radius_y()->base_val();
|
||||
auto morphology_operator = morphology_primitive->morphology_operator();
|
||||
switch (morphology_operator) {
|
||||
case Gfx::MorphologyOperator::Erode:
|
||||
root_filter = Gfx::Filter::erode(radius_x, radius_y, input);
|
||||
break;
|
||||
case Gfx::MorphologyOperator::Dilate:
|
||||
root_filter = Gfx::Filter::dilate(radius_x, radius_y, input);
|
||||
break;
|
||||
case Gfx::MorphologyOperator::Unknown:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
update_result_map(*morphology_primitive);
|
||||
} else if (auto* offset_primitive = as_if<SVGFEOffsetElement>(node)) {
|
||||
auto input = resolve_input_filter(offset_primitive->in1()->base_val());
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMorphologyElement.h>
|
||||
#include <LibWeb/SVG/SVGFEOffsetElement.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
@@ -60,6 +61,7 @@ template class SVGFilterPrimitiveStandardAttributes<SVGFEFloodElement>;
|
||||
template class SVGFilterPrimitiveStandardAttributes<SVGFEGaussianBlurElement>;
|
||||
template class SVGFilterPrimitiveStandardAttributes<SVGFEImageElement>;
|
||||
template class SVGFilterPrimitiveStandardAttributes<SVGFEMergeElement>;
|
||||
template class SVGFilterPrimitiveStandardAttributes<SVGFEMorphologyElement>;
|
||||
template class SVGFilterPrimitiveStandardAttributes<SVGFEOffsetElement>;
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Web::SVG::TagNames {
|
||||
__ENUMERATE_SVG_TAG(feImage) \
|
||||
__ENUMERATE_SVG_TAG(feMerge) \
|
||||
__ENUMERATE_SVG_TAG(feMergeNode) \
|
||||
__ENUMERATE_SVG_TAG(feMorphology) \
|
||||
__ENUMERATE_SVG_TAG(feOffset) \
|
||||
__ENUMERATE_SVG_TAG(filter) \
|
||||
__ENUMERATE_SVG_TAG(foreignObject) \
|
||||
|
||||
@@ -402,6 +402,7 @@ libweb_js_bindings(SVG/SVGFEGaussianBlurElement)
|
||||
libweb_js_bindings(SVG/SVGFEImageElement)
|
||||
libweb_js_bindings(SVG/SVGFEMergeElement)
|
||||
libweb_js_bindings(SVG/SVGFEMergeNodeElement)
|
||||
libweb_js_bindings(SVG/SVGFEMorphologyElement)
|
||||
libweb_js_bindings(SVG/SVGFEOffsetElement)
|
||||
libweb_js_bindings(SVG/SVGFilterElement)
|
||||
libweb_js_bindings(SVG/SVGForeignObjectElement)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<div style="width: 100px; height: 100px; background-color: green"></div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<title>feMorphology filter on mirrored content</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feMorphologyElement">
|
||||
<link rel="match" href="../../../../expected/wpt-import/css/filter-effects/reference/green-100x100.html">
|
||||
<svg>
|
||||
<filter id="dilate" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feMorphology operator="dilate" radius="10"/>
|
||||
</filter>
|
||||
<rect width="100" height="100" fill="red"/>
|
||||
<rect x="10" y="10" width="80" height="30" fill="green" filter="url(#dilate)"
|
||||
transform="translate(0, 50) scale(1, -1)"/>
|
||||
<rect x="10" y="60" width="80" height="30" fill="green" filter="url(#dilate)"
|
||||
transform="translate(100, 0) scale(-1, 1)"/>
|
||||
</svg>
|
||||
@@ -390,6 +390,7 @@ SVGFEGaussianBlurElement
|
||||
SVGFEImageElement
|
||||
SVGFEMergeElement
|
||||
SVGFEMergeNodeElement
|
||||
SVGFEMorphologyElement
|
||||
SVGFEOffsetElement
|
||||
SVGFilterElement
|
||||
SVGForeignObjectElement
|
||||
|
||||
Reference in New Issue
Block a user