LibWeb: Implement SVGFEColorMatrixElement and feColorMatrix

This commit is contained in:
Pavel Shliak 2025-09-22 02:47:28 +04:00 committed by Jelle Raaijmakers
parent b50b89b4a8
commit 5ee1031b89
Notes: github-actions[bot] 2025-10-23 11:37:46 +00:00
21 changed files with 367 additions and 0 deletions

View file

@ -94,6 +94,7 @@ namespace Web::SVG::AttributeNames {
__ENUMERATE_SVG_ATTRIBUTE(targetY, "targetY") \
__ENUMERATE_SVG_ATTRIBUTE(textLength, "textLength") \
__ENUMERATE_SVG_ATTRIBUTE(type, "type") \
__ENUMERATE_SVG_ATTRIBUTE(values, "values") \
__ENUMERATE_SVG_ATTRIBUTE(version, "version") \
__ENUMERATE_SVG_ATTRIBUTE(viewBox, "viewBox") \
__ENUMERATE_SVG_ATTRIBUTE(viewTarget, "viewTarget") \

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2025, Pavel Shliak <shlyakpavel@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/SVGFEColorMatrixElementPrototype.h>
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
#include <LibWeb/SVG/SVGAnimatedString.h>
#include <LibWeb/SVG/SVGFEColorMatrixElement.h>
namespace Web::SVG {
GC_DEFINE_ALLOCATOR(SVGFEColorMatrixElement);
SVGFEColorMatrixElement::SVGFEColorMatrixElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: SVGElement(document, qualified_name)
{
}
void SVGFEColorMatrixElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGFEColorMatrixElement);
Base::initialize(realm);
}
void SVGFEColorMatrixElement::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
SVGFilterPrimitiveStandardAttributes::visit_edges(visitor);
visitor.visit(m_in1);
visitor.visit(m_values);
}
GC::Ref<SVGAnimatedString> SVGFEColorMatrixElement::in1()
{
if (!m_in1)
m_in1 = SVGAnimatedString::create(realm(), *this, AttributeNames::in);
return *m_in1;
}
GC::Ref<SVGAnimatedEnumeration> SVGFEColorMatrixElement::type() const
{
// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEColorMatrixElement
// Map the 'type' attribute to the IDL enumeration values.
// Defaults to MATRIX when omitted.
auto type_attribute = attribute(AttributeNames::type).value_or(String {});
u16 enum_value = SVGFEColorMatrixElement::SVG_FECOLORMATRIX_TYPE_UNKNOWN;
if (type_attribute.is_empty() || type_attribute.equals_ignoring_ascii_case("matrix"sv))
enum_value = SVGFEColorMatrixElement::SVG_FECOLORMATRIX_TYPE_MATRIX;
else if (type_attribute.equals_ignoring_ascii_case("saturate"sv))
enum_value = SVGFEColorMatrixElement::SVG_FECOLORMATRIX_TYPE_SATURATE;
else if (type_attribute.equals_ignoring_ascii_case("hueRotate"sv))
enum_value = SVGFEColorMatrixElement::SVG_FECOLORMATRIX_TYPE_HUEROTATE;
else if (type_attribute.equals_ignoring_ascii_case("luminanceToAlpha"sv))
enum_value = SVGFEColorMatrixElement::SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA;
return SVGAnimatedEnumeration::create(realm(), enum_value);
}
GC::Ref<SVGAnimatedString> SVGFEColorMatrixElement::values()
{
if (!m_values)
m_values = SVGAnimatedString::create(realm(), *this, AttributeNames::values);
return *m_values;
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2025, Pavel Shliak <shlyakpavel@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/SVG/SVGElement.h>
#include <LibWeb/SVG/SVGFilterPrimitiveStandardAttributes.h>
namespace Web::SVG {
class SVGAnimatedEnumeration;
class SVGAnimatedString;
// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEColorMatrixElement
class SVGFEColorMatrixElement final
: public SVGElement
, public SVGFilterPrimitiveStandardAttributes<SVGFEColorMatrixElement> {
WEB_PLATFORM_OBJECT(SVGFEColorMatrixElement, SVGElement);
GC_DECLARE_ALLOCATOR(SVGFEColorMatrixElement);
public:
virtual ~SVGFEColorMatrixElement() override = default;
static constexpr unsigned short SVG_FECOLORMATRIX_TYPE_UNKNOWN = 0;
static constexpr unsigned short SVG_FECOLORMATRIX_TYPE_MATRIX = 1;
static constexpr unsigned short SVG_FECOLORMATRIX_TYPE_SATURATE = 2;
static constexpr unsigned short SVG_FECOLORMATRIX_TYPE_HUEROTATE = 3;
static constexpr unsigned short SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA = 4;
// IDL attributes
GC::Ref<SVGAnimatedString> in1();
GC::Ref<SVGAnimatedEnumeration> type() const;
GC::Ref<SVGAnimatedString> values();
private:
SVGFEColorMatrixElement(DOM::Document&, DOM::QualifiedName);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
GC::Ptr<SVGAnimatedString> m_in1;
GC::Ptr<SVGAnimatedString> m_values;
};
}

View file

@ -0,0 +1,23 @@
#import <SVG/SVGAnimatedString.idl>
#import <SVG/SVGAnimatedEnumeration.idl>
#import <SVG/SVGElement.idl>
#import <SVG/SVGFilterPrimitiveStandardAttributes.idl>
// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEColorMatrixElement
[Exposed=Window]
interface SVGFEColorMatrixElement : SVGElement {
// Color Matrix Types
const unsigned short SVG_FECOLORMATRIX_TYPE_UNKNOWN = 0;
const unsigned short SVG_FECOLORMATRIX_TYPE_MATRIX = 1;
const unsigned short SVG_FECOLORMATRIX_TYPE_SATURATE = 2;
const unsigned short SVG_FECOLORMATRIX_TYPE_HUEROTATE = 3;
const unsigned short SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA = 4;
readonly attribute SVGAnimatedString in1;
readonly attribute SVGAnimatedEnumeration type;
// FIXME: Use SVGAnimatedNumberList when implemented.
readonly attribute SVGAnimatedString values;
};
SVGFEColorMatrixElement includes SVGFilterPrimitiveStandardAttributes;

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringConversions.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibWeb/Bindings/SVGFilterElementPrototype.h>
#include <LibWeb/CSS/Parser/Parser.h>
@ -12,6 +13,7 @@
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/SVG/SVGFEBlendElement.h>
#include <LibWeb/SVG/SVGFEColorMatrixElement.h>
#include <LibWeb/SVG/SVGFECompositeElement.h>
#include <LibWeb/SVG/SVGFEFloodElement.h>
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
@ -140,6 +142,83 @@ Optional<Gfx::Filter> SVGFilterElement::gfx_filter(Layout::NodeWithStyle const&
root_filter = Gfx::Filter::blur(radius_x, radius_y, input);
update_result_map(*blur_primitive);
} else if (auto* colormatrix_primitive = as_if<SVGFEColorMatrixElement>(node)) {
auto in_attr = colormatrix_primitive->in1()->base_val();
auto input = resolve_input_filter(in_attr);
auto type_value = colormatrix_primitive->attribute(AttributeNames::type).value_or(String {});
auto values_value = colormatrix_primitive->attribute(AttributeNames::values).value_or(String {});
// Default type is "matrix" per spec.
if (type_value.is_empty() || type_value.equals_ignoring_ascii_case("matrix"sv)) {
// Parse up to 20 numbers; if we don't get a full 4x5, skip applying.
float matrix[20] = { 0 };
size_t count = 0;
StringView sv = values_value;
auto skip_leading_whitespace = [&] {
sv = sv.trim_whitespace(AK::TrimMode::Left);
};
auto consume_comma_and_whitespace = [&] {
if (!sv.is_empty() && sv[0] == ',')
sv = sv.substring_view(1);
skip_leading_whitespace();
};
skip_leading_whitespace();
while (!sv.is_empty() && count < 20) {
// Parse the next number without trimming (we already trimmed on the left).
auto result = AK::parse_first_number<float>(sv, AK::TrimWhitespace::No);
if (!result.has_value())
break;
matrix[count++] = result->value;
// Advance exactly past the number just parsed, then consume optional comma + whitespace.
sv = sv.substring_view(result->characters_parsed);
consume_comma_and_whitespace();
}
if (count == 20) {
root_filter = Gfx::Filter::color_matrix(matrix, input);
update_result_map(*colormatrix_primitive);
} else {
// If invalid or missing, treat as identity (no-op) if we already have an input.
if (input.has_value()) {
root_filter = input;
update_result_map(*colormatrix_primitive);
}
}
} else if (type_value.equals_ignoring_ascii_case("saturate"sv)) {
// values: single number s (1 = original)
float s = 1.0f;
if (!values_value.is_empty()) {
if (auto parsed = AK::parse_number<float>(values_value, AK::TrimWhitespace::Yes); parsed.has_value())
s = *parsed;
}
root_filter = Gfx::Filter::saturate(s, input);
update_result_map(*colormatrix_primitive);
} else if (type_value.equals_ignoring_ascii_case("hueRotate"sv)) {
// values: angle in degrees
float angle_degrees = 0.0f;
if (!values_value.is_empty()) {
if (auto parsed = AK::parse_number<float>(values_value, AK::TrimWhitespace::Yes); parsed.has_value())
angle_degrees = *parsed;
}
root_filter = Gfx::Filter::hue_rotate(angle_degrees, input);
update_result_map(*colormatrix_primitive);
} else if (type_value.equals_ignoring_ascii_case("luminanceToAlpha"sv)) {
// values ignored; convert luminance to alpha and zero RGB.
float matrix[20] = {
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0.2126f, 0.7152f, 0.0722f, 0, 0
};
root_filter = Gfx::Filter::color_matrix(matrix, input);
update_result_map(*colormatrix_primitive);
} else {
// Unknown 'type' value on feColorMatrix; skip creating a filter and log.
dbgln("SVGFEColorMatrixElement: Unknown type '{}' — skipping filter primitive", type_value);
}
} else if (auto* image_primitive = as_if<SVGFEImageElement>(node)) {
auto bitmap = image_primitive->current_image_bitmap({});
if (!bitmap)

View file

@ -18,6 +18,7 @@ namespace Web::SVG::TagNames {
__ENUMERATE_SVG_TAG(desc) \
__ENUMERATE_SVG_TAG(ellipse) \
__ENUMERATE_SVG_TAG(feBlend) \
__ENUMERATE_SVG_TAG(feColorMatrix) \
__ENUMERATE_SVG_TAG(feComposite) \
__ENUMERATE_SVG_TAG(feFlood) \
__ENUMERATE_SVG_TAG(feGaussianBlur) \