mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-21 16:58:58 +00:00
LibWeb: Implement <feImage>
SVG filter
This commit is contained in:
parent
5eff541804
commit
d4f05bc4ef
Notes:
github-actions[bot]
2025-08-29 09:16:34 +00:00
Author: https://github.com/tcl3
Commit: d4f05bc4ef
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5856
Reviewed-by: https://github.com/AtkinsSJ ✅
Reviewed-by: https://github.com/gmta
19 changed files with 242 additions and 3 deletions
|
@ -224,6 +224,15 @@ Filter Filter::hue_rotate(float angle_degrees, Optional<Filter const&> input)
|
|||
return Filter(Impl::create(SkImageFilters::ColorFilter(color_filter, input_skia)));
|
||||
}
|
||||
|
||||
Filter Filter::image(Gfx::ImmutableBitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::IntRect const& dest_rect, Gfx::ScalingMode scaling_mode)
|
||||
{
|
||||
auto skia_src_rect = to_skia_rect(src_rect);
|
||||
auto skia_dest_rect = to_skia_rect(dest_rect);
|
||||
auto sampling_options = to_skia_sampling_options(scaling_mode);
|
||||
|
||||
return Filter(Impl::create(SkImageFilters::Image(sk_ref_sp(bitmap.sk_image()), skia_src_rect, skia_dest_rect, sampling_options)));
|
||||
}
|
||||
|
||||
Filter Filter::merge(Vector<Optional<Filter>> const& inputs)
|
||||
{
|
||||
Vector<sk_sp<SkImageFilter>> skia_filters;
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/ScalingMode.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
|
@ -40,6 +43,7 @@ public:
|
|||
static Filter color_matrix(float matrix[20], Optional<Filter const&> input = {});
|
||||
static Filter saturate(float value, Optional<Filter const&> input = {});
|
||||
static Filter hue_rotate(float angle_degrees, Optional<Filter const&> input = {});
|
||||
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 = {});
|
||||
|
||||
|
|
|
@ -874,6 +874,7 @@ set(SOURCES
|
|||
SVG/SVGFEBlendElement.cpp
|
||||
SVG/SVGFEFloodElement.cpp
|
||||
SVG/SVGFEGaussianBlurElement.cpp
|
||||
SVG/SVGFEImageElement.cpp
|
||||
SVG/SVGFEMergeElement.cpp
|
||||
SVG/SVGFEMergeNodeElement.cpp
|
||||
SVG/SVGFEOffsetElement.cpp
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeNodeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEOffsetElement.h>
|
||||
|
@ -474,6 +475,8 @@ static GC::Ref<SVG::SVGElement> create_svg_element(JS::Realm& realm, Document& d
|
|||
return realm.create<SVG::SVGFEFloodElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feGaussianBlur)
|
||||
return realm.create<SVG::SVGFEGaussianBlurElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feImage)
|
||||
return realm.create<SVG::SVGFEImageElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feMerge)
|
||||
return realm.create<SVG::SVGFEMergeElement>(document, move(qualified_name));
|
||||
if (local_name == SVG::TagNames::feMergeNode)
|
||||
|
|
|
@ -106,6 +106,7 @@ enum class StyleInvalidationReason {
|
|||
X(LayoutTreeUpdate) \
|
||||
X(NavigableSetViewportSize) \
|
||||
X(SVGImageElementFetchTheDocument) \
|
||||
X(SVGImageFilterFetch) \
|
||||
X(StyleChange)
|
||||
|
||||
enum class SetNeedsLayoutReason {
|
||||
|
|
|
@ -1051,6 +1051,7 @@ class SVGEllipseElement;
|
|||
class SVGFEBlendElement;
|
||||
class SVGFEFloodElement;
|
||||
class SVGFEGaussianBlurElement;
|
||||
class SVGFEImageElement;
|
||||
class SVGFilterElement;
|
||||
class SVGFitToViewBox;
|
||||
class SVGForeignObjectElement;
|
||||
|
|
|
@ -1695,7 +1695,8 @@ Optional<Gfx::Filter> PaintableBox::resolve_filter(CSS::Filter const& computed_f
|
|||
return;
|
||||
|
||||
if (auto* filter_element = as_if<SVG::SVGFilterElement>(*maybe_filter)) {
|
||||
auto new_filter = filter_element->gfx_filter();
|
||||
auto& layout_node = layout_node_with_style_and_box_metrics();
|
||||
auto new_filter = filter_element->gfx_filter(layout_node);
|
||||
|
||||
if (!new_filter.has_value())
|
||||
return;
|
||||
|
|
113
Libraries/LibWeb/SVG/SVGFEImageElement.cpp
Normal file
113
Libraries/LibWeb/SVG/SVGFEImageElement.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SVGFEImageElement.h"
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibWeb/Bindings/SVGFEImageElementPrototype.h>
|
||||
#include <LibWeb/HTML/DecodedImageData.h>
|
||||
#include <LibWeb/HTML/PotentialCORSRequest.h>
|
||||
#include <LibWeb/HTML/SharedResourceRequest.h>
|
||||
#include <LibWeb/Layout/SVGImageBox.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(SVGFEImageElement);
|
||||
|
||||
SVGFEImageElement::SVGFEImageElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: SVGElement(document, qualified_name)
|
||||
{
|
||||
}
|
||||
|
||||
void SVGFEImageElement::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEImageElement);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void SVGFEImageElement::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
SVGFilterPrimitiveStandardAttributes::visit_edges(visitor);
|
||||
SVGURIReferenceMixin::visit_edges(visitor);
|
||||
visitor.visit(m_resource_request);
|
||||
}
|
||||
|
||||
void SVGFEImageElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
|
||||
{
|
||||
Base::attribute_changed(name, old_value, value, namespace_);
|
||||
|
||||
if (name == SVG::AttributeNames::href) {
|
||||
if (namespace_ == Namespace::XLink && has_attribute_ns({}, name))
|
||||
return;
|
||||
|
||||
auto href = value;
|
||||
if (!namespace_.has_value() && !href.has_value())
|
||||
href = get_attribute_ns(SVG::AttributeNames::href, Namespace::XLink);
|
||||
|
||||
process_href(href);
|
||||
}
|
||||
}
|
||||
|
||||
void SVGFEImageElement::process_href(Optional<String> const& href)
|
||||
{
|
||||
if (!href.has_value()) {
|
||||
m_href = {};
|
||||
return;
|
||||
}
|
||||
|
||||
m_href = document().encoding_parse_url(*href);
|
||||
if (!m_href.has_value())
|
||||
return;
|
||||
|
||||
m_resource_request = HTML::SharedResourceRequest::get_or_create(realm(), document().page(), *m_href);
|
||||
m_resource_request->add_callbacks(
|
||||
[this, resource_request = GC::Root { m_resource_request }] {
|
||||
set_needs_style_update(true);
|
||||
if (auto layout_node = this->layout_node())
|
||||
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::SVGImageFilterFetch);
|
||||
},
|
||||
nullptr);
|
||||
|
||||
if (m_resource_request->needs_fetching()) {
|
||||
auto request = HTML::create_potential_CORS_request(vm(), *m_href, Fetch::Infrastructure::Request::Destination::Image, HTML::CORSSettingAttribute::NoCORS);
|
||||
request->set_client(&document().relevant_settings_object());
|
||||
m_resource_request->fetch_resource(realm(), request);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> SVGFEImageElement::current_image_bitmap(Gfx::IntSize size) const
|
||||
{
|
||||
if (!m_resource_request)
|
||||
return {};
|
||||
if (auto data = m_resource_request->image_data())
|
||||
return data->bitmap(0, size);
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<Gfx::IntRect> SVGFEImageElement::content_rect() const
|
||||
{
|
||||
auto bitmap = current_image_bitmap();
|
||||
if (!bitmap)
|
||||
return {};
|
||||
auto layout_node = this->layout_node();
|
||||
if (!layout_node)
|
||||
return {};
|
||||
auto width = layout_node->computed_values().width().to_px(*layout_node, 0);
|
||||
if (width == 0)
|
||||
width = bitmap->width();
|
||||
|
||||
auto height = layout_node->computed_values().height().to_px(*layout_node, 0);
|
||||
if (height == 0)
|
||||
height = bitmap->height();
|
||||
|
||||
auto x = layout_node->computed_values().x().to_px(*layout_node, 0);
|
||||
auto y = layout_node->computed_values().y().to_px(*layout_node, 0);
|
||||
return Gfx::enclosing_int_rect({ x, y, width, height });
|
||||
}
|
||||
|
||||
}
|
43
Libraries/LibWeb/SVG/SVGFEImageElement.h
Normal file
43
Libraries/LibWeb/SVG/SVGFEImageElement.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/SVG/SVGElement.h>
|
||||
#include <LibWeb/SVG/SVGFilterPrimitiveStandardAttributes.h>
|
||||
#include <LibWeb/SVG/SVGURIReference.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
class SVGFEImageElement final
|
||||
: public SVGElement
|
||||
, public SVGFilterPrimitiveStandardAttributes<SVGFEImageElement>
|
||||
, public SVGURIReferenceMixin<SupportsXLinkHref::Yes> {
|
||||
WEB_PLATFORM_OBJECT(SVGFEImageElement, SVGElement);
|
||||
GC_DECLARE_ALLOCATOR(SVGFEImageElement);
|
||||
|
||||
public:
|
||||
virtual ~SVGFEImageElement() override = default;
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> current_image_bitmap(Gfx::IntSize = {}) const;
|
||||
Optional<Gfx::IntRect> content_rect() const;
|
||||
|
||||
private:
|
||||
SVGFEImageElement(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& value, Optional<FlyString> const& namespace_) override;
|
||||
|
||||
void process_href(Optional<String> const& href);
|
||||
|
||||
Optional<URL::URL> m_href;
|
||||
|
||||
GC::Ptr<HTML::SharedResourceRequest> m_resource_request;
|
||||
};
|
||||
|
||||
};
|
14
Libraries/LibWeb/SVG/SVGFEImageElement.idl
Normal file
14
Libraries/LibWeb/SVG/SVGFEImageElement.idl
Normal file
|
@ -0,0 +1,14 @@
|
|||
#import <SVG/SVGAnimatedString.idl>
|
||||
#import <SVG/SVGElement.idl>
|
||||
#import <SVG/SVGFilterPrimitiveStandardAttributes.idl>
|
||||
#import <SVG/SVGURIReference.idl>
|
||||
|
||||
// https://www.w3.org/TR/filter-effects-1/#feImageElement
|
||||
[Exposed=Window]
|
||||
interface SVGFEImageElement : SVGElement {
|
||||
[FIXME] readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio;
|
||||
[FIXME] readonly attribute SVGAnimatedString crossOrigin;
|
||||
};
|
||||
|
||||
SVGFEImageElement includes SVGFilterPrimitiveStandardAttributes;
|
||||
SVGFEImageElement includes SVGURIReference;
|
|
@ -5,12 +5,16 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibWeb/Bindings/SVGFilterElementPrototype.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeNodeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEOffsetElement.h>
|
||||
|
@ -77,7 +81,7 @@ void SVGFilterElement::attribute_changed(FlyString const& name, Optional<String>
|
|||
m_primitive_units = AttributeParser::parse_units(value.value_or({}));
|
||||
}
|
||||
|
||||
Optional<Gfx::Filter> SVGFilterElement::gfx_filter()
|
||||
Optional<Gfx::Filter> SVGFilterElement::gfx_filter(Layout::NodeWithStyle const& referenced_node)
|
||||
{
|
||||
HashMap<String, Gfx::Filter> result_map;
|
||||
Optional<Gfx::Filter> root_filter;
|
||||
|
@ -129,6 +133,27 @@ Optional<Gfx::Filter> SVGFilterElement::gfx_filter()
|
|||
|
||||
root_filter = Gfx::Filter::blur(radius_x, radius_y, input);
|
||||
update_result_map(*blur_primitive);
|
||||
} else if (auto* image_primitive = as_if<SVGFEImageElement>(node)) {
|
||||
auto bitmap = image_primitive->current_image_bitmap({});
|
||||
if (!bitmap)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto src_rect = image_primitive->content_rect();
|
||||
if (!src_rect.has_value())
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto* dom_node = referenced_node.dom_node();
|
||||
if (!dom_node)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto* paintable_box = dom_node->paintable_box();
|
||||
if (!paintable_box)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto dest_rect = Gfx::enclosing_int_rect(paintable_box->absolute_rect().to_type<float>());
|
||||
auto scaling_mode = CSS::to_gfx_scaling_mode(paintable_box->computed_values().image_rendering(), *src_rect, dest_rect);
|
||||
root_filter = Gfx::Filter::image(*bitmap, *src_rect, dest_rect, scaling_mode);
|
||||
update_result_map(*image_primitive);
|
||||
} else if (auto* merge_primitive = as_if<SVGFEMergeElement>(node)) {
|
||||
Vector<Optional<Gfx::Filter>> merge_inputs;
|
||||
merge_primitive->template for_each_child_of_type<SVGFEMergeNodeElement>([&](auto& merge_node) {
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
|
||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
||||
|
||||
Optional<Gfx::Filter> gfx_filter();
|
||||
Optional<Gfx::Filter> gfx_filter(Layout::NodeWithStyle const& referenced_node);
|
||||
|
||||
GC::Ref<SVGAnimatedEnumeration> filter_units() const;
|
||||
GC::Ref<SVGAnimatedEnumeration> primitive_units() const;
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Web::SVG::TagNames {
|
|||
__ENUMERATE_SVG_TAG(feBlend) \
|
||||
__ENUMERATE_SVG_TAG(feFlood) \
|
||||
__ENUMERATE_SVG_TAG(feGaussianBlur) \
|
||||
__ENUMERATE_SVG_TAG(feImage) \
|
||||
__ENUMERATE_SVG_TAG(feMerge) \
|
||||
__ENUMERATE_SVG_TAG(feMergeNode) \
|
||||
__ENUMERATE_SVG_TAG(feOffset) \
|
||||
|
|
|
@ -365,6 +365,7 @@ libweb_js_bindings(SVG/SVGEllipseElement)
|
|||
libweb_js_bindings(SVG/SVGFEBlendElement)
|
||||
libweb_js_bindings(SVG/SVGFEFloodElement)
|
||||
libweb_js_bindings(SVG/SVGFEGaussianBlurElement)
|
||||
libweb_js_bindings(SVG/SVGFEImageElement)
|
||||
libweb_js_bindings(SVG/SVGFEMergeElement)
|
||||
libweb_js_bindings(SVG/SVGFEMergeNodeElement)
|
||||
libweb_js_bindings(SVG/SVGFEOffsetElement)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<img src="../support/color-palette.png">
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<title>CSS Filters: feImage and CSS reference filters.</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feImageElement">
|
||||
<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
|
||||
<link rel="match" href="../../../../expected/wpt-import/css/filter-effects/reference/effect-reference-feimage-001-ref.html">
|
||||
<meta name="assert" content="This test ensures that CSS reference filters supports feImage."/>
|
||||
<style>
|
||||
#filtered {
|
||||
width: 160px;
|
||||
height: 90px;
|
||||
filter: url(#imagereplace);
|
||||
}
|
||||
</style>
|
||||
<div id="filtered"></div>
|
||||
<svg width="0" height="0">
|
||||
<filter id="imagereplace" x="0%" y="0%" width="100%" height="100%">
|
||||
<feimage xlink:href="support/color-palette.png"/>
|
||||
</filter>
|
||||
</svg>
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
|
@ -354,6 +354,7 @@ SVGEllipseElement
|
|||
SVGFEBlendElement
|
||||
SVGFEFloodElement
|
||||
SVGFEGaussianBlurElement
|
||||
SVGFEImageElement
|
||||
SVGFEMergeElement
|
||||
SVGFEMergeNodeElement
|
||||
SVGFEOffsetElement
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue