mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Hook up SVG component transfer filter to Skia
This commit is contained in:
parent
70e98e72a8
commit
b4810f47a3
Notes:
github-actions[bot]
2025-11-09 00:23:44 +00:00
Author: https://github.com/gmta
Commit: b4810f47a3
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6709
8 changed files with 264 additions and 1 deletions
|
|
@ -47,6 +47,9 @@ void SVGComponentTransferFunctionElement::attribute_changed(FlyString const& nam
|
|||
// FIXME: Support reflection instead of invalidating the list.
|
||||
if (name == AttributeNames::tableValues)
|
||||
m_table_values = {};
|
||||
|
||||
// Clear our cached color table on any attribute change.
|
||||
m_cached_color_table.clear();
|
||||
}
|
||||
|
||||
void SVGComponentTransferFunctionElement::initialize(JS::Realm& realm)
|
||||
|
|
@ -144,4 +147,117 @@ SVGComponentTransferFunctionElement::Type SVGComponentTransferFunctionElement::t
|
|||
return parse_type(get_attribute_value(AttributeNames::type));
|
||||
}
|
||||
|
||||
Vector<float> SVGComponentTransferFunctionElement::table_float_values()
|
||||
{
|
||||
Vector<float> values;
|
||||
auto table_numbers = table_values()->base_val()->items();
|
||||
values.ensure_capacity(table_numbers.size());
|
||||
for (auto& svg_number : table_numbers)
|
||||
values.unchecked_append(svg_number->value());
|
||||
return values;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#element-attrdef-fecomponenttransfer-type
|
||||
ReadonlyBytes SVGComponentTransferFunctionElement::color_table()
|
||||
{
|
||||
if (m_cached_color_table.has_value())
|
||||
return m_cached_color_table.value();
|
||||
|
||||
ByteBuffer result;
|
||||
result.resize(256);
|
||||
|
||||
auto set_identity = [&result] {
|
||||
for (int i = 0; i < 256; ++i)
|
||||
result.data()[i] = i;
|
||||
};
|
||||
auto to_u8 = [](float value) {
|
||||
return AK::clamp_to<u8>(value * 255.f);
|
||||
};
|
||||
|
||||
switch (type_from_attribute()) {
|
||||
// https://drafts.fxtf.org/filter-effects/#attr-valuedef-type-identity
|
||||
case Type::Unknown:
|
||||
case Type::Identity:
|
||||
set_identity();
|
||||
break;
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#attr-valuedef-type-table
|
||||
case Type::Table: {
|
||||
auto table_values = table_float_values();
|
||||
|
||||
// An empty list results in an identity transfer function.
|
||||
if (table_values.is_empty()) {
|
||||
set_identity();
|
||||
break;
|
||||
}
|
||||
|
||||
// For a value C < 1 find k such that: k/n <= C < (k+1)/n
|
||||
// The result C' is given by: C' = vk + (C - k/n)*n * (vk+1 - vk)
|
||||
auto const segments = table_values.size() - 1.f;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
// If C = 1 then: C' = vn.
|
||||
if (i == 255 || segments == 0.f) {
|
||||
result.data()[i] = to_u8(table_values.last());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto offset = i / 255.f;
|
||||
auto segment_index = static_cast<size_t>(offset * segments);
|
||||
auto segment_start = segment_index / segments;
|
||||
auto offset_in_segment = offset - segment_start;
|
||||
auto segment_length = 1.f / segments;
|
||||
auto progress_in_segment = offset_in_segment / segment_length;
|
||||
|
||||
auto segment_value = mix(table_values[segment_index], table_values[segment_index + 1], progress_in_segment);
|
||||
result.data()[i] = to_u8(segment_value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#attr-valuedef-type-discrete
|
||||
case Type::Discrete: {
|
||||
auto table_values = table_float_values();
|
||||
|
||||
// An empty list results in an identity transfer function.
|
||||
if (table_values.is_empty()) {
|
||||
set_identity();
|
||||
break;
|
||||
}
|
||||
|
||||
// For a value C < 1 find k such that: k/n <= C < (k+1)/n
|
||||
// The result C' is given by: C' = vk + (C - k/n)*n * (vk+1 - vk)
|
||||
for (int i = 0; i < 255; ++i) {
|
||||
auto index = static_cast<size_t>(i / 255.f * table_values.size());
|
||||
result.data()[i] = to_u8(table_values[index]);
|
||||
}
|
||||
|
||||
// If C = 1 then: C' = vn.
|
||||
result.data()[255] = to_u8(table_values.last());
|
||||
break;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#attr-valuedef-type-linear
|
||||
case Type::Linear: {
|
||||
auto slope = this->slope()->base_val();
|
||||
auto intercept = this->intercept()->base_val();
|
||||
for (int i = 0; i < 256; ++i)
|
||||
result.data()[i] = to_u8(slope * i / 255.f + intercept);
|
||||
break;
|
||||
}
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#attr-valuedef-type-gamma
|
||||
case Type::Gamma: {
|
||||
auto amplitude = this->amplitude()->base_val();
|
||||
auto exponent = this->exponent()->base_val();
|
||||
auto offset = this->offset()->base_val();
|
||||
for (int i = 0; i < 256; ++i)
|
||||
result.data()[i] = to_u8(amplitude * pow(i / 255.f, exponent) + offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_cached_color_table = move(result);
|
||||
return m_cached_color_table.value();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedEnumeration.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedNumber.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedNumberList.h>
|
||||
|
|
@ -40,6 +42,9 @@ public:
|
|||
GC::Ref<SVGAnimatedNumber> exponent();
|
||||
GC::Ref<SVGAnimatedNumber> offset();
|
||||
|
||||
Vector<float> table_float_values();
|
||||
ReadonlyBytes color_table();
|
||||
|
||||
protected:
|
||||
SVGComponentTransferFunctionElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
@ -57,6 +62,8 @@ private:
|
|||
GC::Ptr<SVGAnimatedNumber> m_amplitude;
|
||||
GC::Ptr<SVGAnimatedNumber> m_exponent;
|
||||
GC::Ptr<SVGAnimatedNumber> m_offset;
|
||||
|
||||
Optional<ByteBuffer> m_cached_color_table;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,16 @@
|
|||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/SVG/SVGComponentTransferFunctionElement.h>
|
||||
#include <LibWeb/SVG/SVGFEBlendElement.h>
|
||||
#include <LibWeb/SVG/SVGFEColorMatrixElement.h>
|
||||
#include <LibWeb/SVG/SVGFEComponentTransferElement.h>
|
||||
#include <LibWeb/SVG/SVGFECompositeElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFloodElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFuncAElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFuncBElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFuncGElement.h>
|
||||
#include <LibWeb/SVG/SVGFEFuncRElement.h>
|
||||
#include <LibWeb/SVG/SVGFEGaussianBlurElement.h>
|
||||
#include <LibWeb/SVG/SVGFEImageElement.h>
|
||||
#include <LibWeb/SVG/SVGFEMergeElement.h>
|
||||
|
|
@ -101,7 +106,33 @@ 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* component_transfer = as_if<SVGFEComponentTransferElement>(node)) {
|
||||
dbgln("FIXME: Implement support for SVGFEComponentTransferElement");
|
||||
auto input = resolve_input_filter(component_transfer->in1()->base_val());
|
||||
|
||||
// https://drafts.fxtf.org/filter-effects/#feComponentTransferElement
|
||||
// * If more than one transfer function element of the same kind is specified, the last occurrence is to be
|
||||
// used.
|
||||
// * If any of the transfer function elements are unspecified, the feComponentTransfer must be processed as
|
||||
// if those transfer function elements were specified with their type attributes set to identity.
|
||||
Array<GC::Ptr<SVGComponentTransferFunctionElement>, 4> argb_function_elements;
|
||||
node.for_each_child([&](auto& child) {
|
||||
if (auto* func_a = as_if<SVGFEFuncAElement>(child))
|
||||
argb_function_elements[0] = func_a;
|
||||
else if (auto* func_r = as_if<SVGFEFuncRElement>(child))
|
||||
argb_function_elements[1] = func_r;
|
||||
else if (auto* func_g = as_if<SVGFEFuncGElement>(child))
|
||||
argb_function_elements[2] = func_g;
|
||||
else if (auto* func_b = as_if<SVGFEFuncBElement>(child))
|
||||
argb_function_elements[3] = func_b;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
root_filter = Gfx::Filter::color_table(
|
||||
argb_function_elements[0] ? argb_function_elements[0]->color_table() : Optional<ReadonlyBytes> {},
|
||||
argb_function_elements[1] ? argb_function_elements[1]->color_table() : Optional<ReadonlyBytes> {},
|
||||
argb_function_elements[2] ? argb_function_elements[2]->color_table() : Optional<ReadonlyBytes> {},
|
||||
argb_function_elements[3] ? argb_function_elements[3]->color_table() : Optional<ReadonlyBytes> {},
|
||||
input);
|
||||
update_result_map(*component_transfer);
|
||||
} 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());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue