LibWeb: Resolve backdrop filter length in apply_style()

Instead of resolving lengths used in the backdrop-filter during
painting, we can do that earlier in apply_style().

This change moves us a bit closer to the point when the stacking
context tree will be completely separated from the layout tree :)
This commit is contained in:
Aliaksandr Kalenik 2023-10-12 01:34:20 +02:00 committed by Andreas Kling
parent 6528f6db26
commit 7803dcfcf9
7 changed files with 74 additions and 37 deletions

View file

@ -47,6 +47,34 @@ struct QuotesData {
Vector<Array<String, 2>> strings {}; Vector<Array<String, 2>> strings {};
}; };
struct ResolvedBackdropFilter {
struct Blur {
float radius;
};
struct DropShadow {
double offset_x;
double offset_y;
double radius;
Color color;
};
struct HueRotate {
float angle_degrees;
};
struct ColorOperation {
Filter::Color::Operation operation;
float amount;
};
using FilterFunction = Variant<Blur, DropShadow, HueRotate, ColorOperation>;
bool is_none() const { return filters.size() == 0; }
Vector<FilterFunction> filters;
};
class InitialValues { class InitialValues {
public: public:
static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; } static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; }
@ -71,7 +99,7 @@ public:
static CSS::Display display() { return CSS::Display { CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow }; } static CSS::Display display() { return CSS::Display { CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow }; }
static Color color() { return Color::Black; } static Color color() { return Color::Black; }
static Color stop_color() { return Color::Black; } static Color stop_color() { return Color::Black; }
static CSS::BackdropFilter backdrop_filter() { return BackdropFilter::make_none(); } static CSS::ResolvedBackdropFilter backdrop_filter() { return ResolvedBackdropFilter { .filters = {} }; }
static Color background_color() { return Color::Transparent; } static Color background_color() { return Color::Transparent; }
static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; } static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; }
static CSS::ListStylePosition list_style_position() { return CSS::ListStylePosition::Outside; } static CSS::ListStylePosition list_style_position() { return CSS::ListStylePosition::Outside; }
@ -302,7 +330,7 @@ public:
CSS::JustifyContent justify_content() const { return m_noninherited.justify_content; } CSS::JustifyContent justify_content() const { return m_noninherited.justify_content; }
CSS::JustifySelf justify_self() const { return m_noninherited.justify_self; } CSS::JustifySelf justify_self() const { return m_noninherited.justify_self; }
CSS::JustifyItems justify_items() const { return m_noninherited.justify_items; } CSS::JustifyItems justify_items() const { return m_noninherited.justify_items; }
CSS::BackdropFilter const& backdrop_filter() const { return m_noninherited.backdrop_filter; } CSS::ResolvedBackdropFilter const& backdrop_filter() const { return m_noninherited.backdrop_filter; }
Vector<ShadowData> const& box_shadow() const { return m_noninherited.box_shadow; } Vector<ShadowData> const& box_shadow() const { return m_noninherited.box_shadow; }
CSS::BoxSizing box_sizing() const { return m_noninherited.box_sizing; } CSS::BoxSizing box_sizing() const { return m_noninherited.box_sizing; }
CSS::Size const& width() const { return m_noninherited.width; } CSS::Size const& width() const { return m_noninherited.width; }
@ -452,7 +480,7 @@ protected:
CSS::LengthBox inset { InitialValues::inset() }; CSS::LengthBox inset { InitialValues::inset() };
CSS::LengthBox margin { InitialValues::margin() }; CSS::LengthBox margin { InitialValues::margin() };
CSS::LengthBox padding { InitialValues::padding() }; CSS::LengthBox padding { InitialValues::padding() };
CSS::BackdropFilter backdrop_filter { InitialValues::backdrop_filter() }; CSS::ResolvedBackdropFilter backdrop_filter { InitialValues::backdrop_filter() };
BorderData border_left; BorderData border_left;
BorderData border_top; BorderData border_top;
BorderData border_right; BorderData border_right;
@ -565,7 +593,7 @@ public:
void set_list_style_type(CSS::ListStyleType value) { m_inherited.list_style_type = value; } void set_list_style_type(CSS::ListStyleType value) { m_inherited.list_style_type = value; }
void set_list_style_position(CSS::ListStylePosition value) { m_inherited.list_style_position = value; } void set_list_style_position(CSS::ListStylePosition value) { m_inherited.list_style_position = value; }
void set_display(CSS::Display value) { m_noninherited.display = value; } void set_display(CSS::Display value) { m_noninherited.display = value; }
void set_backdrop_filter(CSS::BackdropFilter backdrop_filter) { m_noninherited.backdrop_filter = move(backdrop_filter); } void set_backdrop_filter(CSS::ResolvedBackdropFilter backdrop_filter) { m_noninherited.backdrop_filter = move(backdrop_filter); }
void set_border_bottom_left_radius(CSS::BorderRadiusData value) { m_noninherited.border_bottom_left_radius = move(value); } void set_border_bottom_left_radius(CSS::BorderRadiusData value) { m_noninherited.border_bottom_left_radius = move(value); }
void set_border_bottom_right_radius(CSS::BorderRadiusData value) { m_noninherited.border_bottom_right_radius = move(value); } void set_border_bottom_right_radius(CSS::BorderRadiusData value) { m_noninherited.border_bottom_right_radius = move(value); }
void set_border_top_left_radius(CSS::BorderRadiusData value) { m_noninherited.border_top_left_radius = move(value); } void set_border_top_left_radius(CSS::BorderRadiusData value) { m_noninherited.border_top_left_radius = move(value); }

View file

@ -23,18 +23,6 @@ float Filter::Blur::resolved_radius(Layout::Node const& node) const
return sigma * 2; return sigma * 2;
} }
Filter::DropShadow::Resolved Filter::DropShadow::resolved(Layout::Node const& node) const
{
// The default value for omitted values is missing length values set to 0
// and the missing used color is taken from the color property.
return Resolved {
offset_x.to_px(node).to_double(),
offset_y.to_px(node).to_double(),
radius.has_value() ? radius->to_px(node).to_double() : 0.0,
color.has_value() ? *color : node.computed_values().color()
};
}
float Filter::HueRotate::angle_degrees() const float Filter::HueRotate::angle_degrees() const
{ {
// Default value when omitted is 0deg. // Default value when omitted is 0deg.

View file

@ -29,13 +29,6 @@ struct DropShadow {
Length offset_y; Length offset_y;
Optional<Length> radius {}; Optional<Length> radius {};
Optional<Color> color {}; Optional<Color> color {};
struct Resolved {
double offset_x;
double offset_y;
double radius;
Color color;
};
Resolved resolved(Layout::Node const&) const;
bool operator==(DropShadow const&) const = default; bool operator==(DropShadow const&) const = default;
}; };

View file

@ -537,7 +537,35 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
computed_values.set_flex_shrink(computed_style.flex_shrink()); computed_values.set_flex_shrink(computed_style.flex_shrink());
computed_values.set_order(computed_style.order()); computed_values.set_order(computed_style.order());
computed_values.set_clip(computed_style.clip()); computed_values.set_clip(computed_style.clip());
computed_values.set_backdrop_filter(computed_style.backdrop_filter());
if (computed_style.backdrop_filter().has_filters()) {
CSS::ResolvedBackdropFilter resolved_backdrop_filter;
for (auto& filter : computed_style.backdrop_filter().filters()) {
filter.visit(
[&](CSS::Filter::Blur const& blur) {
resolved_backdrop_filter.filters.append(CSS::ResolvedBackdropFilter::Blur {
.radius = blur.resolved_radius(*this) });
},
[&](CSS::Filter::DropShadow const& drop_shadow) {
// The default value for omitted values is missing length values set to 0
// and the missing used color is taken from the color property.
resolved_backdrop_filter.filters.append(CSS::ResolvedBackdropFilter::DropShadow {
.offset_x = drop_shadow.offset_x.to_px(*this).to_double(),
.offset_y = drop_shadow.offset_y.to_px(*this).to_double(),
.radius = drop_shadow.radius.has_value() ? drop_shadow.radius->to_px(*this).to_double() : 0.0,
.color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() });
},
[&](CSS::Filter::Color const& color_operation) {
resolved_backdrop_filter.filters.append(CSS::ResolvedBackdropFilter::ColorOperation {
.operation = color_operation.operation,
.amount = color_operation.resolved_amount() });
},
[&](CSS::Filter::HueRotate const& hue_rotate) {
resolved_backdrop_filter.filters.append(CSS::ResolvedBackdropFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees() });
});
}
computed_values.set_backdrop_filter(resolved_backdrop_filter);
}
auto justify_content = computed_style.justify_content(); auto justify_content = computed_style.justify_content();
if (justify_content.has_value()) if (justify_content.has_value())

View file

@ -19,7 +19,7 @@
namespace Web::Painting { namespace Web::Painting {
void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, ReadonlySpan<CSS::FilterFunction> filter_list) void apply_filter_list(Gfx::Bitmap& target_bitmap, ReadonlySpan<CSS::ResolvedBackdropFilter::FilterFunction> filter_list)
{ {
auto apply_color_filter = [&](Gfx::ColorFilter const& filter) { auto apply_color_filter = [&](Gfx::ColorFilter const& filter) {
const_cast<Gfx::ColorFilter&>(filter).apply(target_bitmap, target_bitmap.rect(), target_bitmap, target_bitmap.rect()); const_cast<Gfx::ColorFilter&>(filter).apply(target_bitmap, target_bitmap.rect(), target_bitmap, target_bitmap.rect());
@ -27,14 +27,14 @@ void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, Rea
for (auto& filter_function : filter_list) { for (auto& filter_function : filter_list) {
// See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions // See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
filter_function.visit( filter_function.visit(
[&](CSS::Filter::Blur const& blur) { [&](CSS::ResolvedBackdropFilter::Blur const& blur_filter) {
// Applies a Gaussian blur to the input image. // Applies a Gaussian blur to the input image.
// The passed parameter defines the value of the standard deviation to the Gaussian function. // The passed parameter defines the value of the standard deviation to the Gaussian function.
Gfx::StackBlurFilter filter { target_bitmap }; Gfx::StackBlurFilter filter { target_bitmap };
filter.process_rgba(blur.resolved_radius(node), Color::Transparent); filter.process_rgba(blur_filter.radius, Color::Transparent);
}, },
[&](CSS::Filter::Color const& color) { [&](CSS::ResolvedBackdropFilter::ColorOperation const& color) {
auto amount = color.resolved_amount(); auto amount = color.amount;
auto amount_clamped = clamp(amount, 0.0f, 1.0f); auto amount_clamped = clamp(amount, 0.0f, 1.0f);
switch (color.operation) { switch (color.operation) {
case CSS::Filter::Color::Operation::Grayscale: { case CSS::Filter::Color::Operation::Grayscale: {
@ -86,19 +86,19 @@ void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, Rea
break; break;
} }
}, },
[&](CSS::Filter::HueRotate const& hue_rotate) { [&](CSS::ResolvedBackdropFilter::HueRotate const& hue_rotate) {
// Applies a hue rotation on the input image. // Applies a hue rotation on the input image.
// The passed parameter defines the number of degrees around the color circle the input samples will be adjusted. // The passed parameter defines the number of degrees around the color circle the input samples will be adjusted.
// A value of 0deg leaves the input unchanged. Implementations must not normalize this value in order to allow animations beyond 360deg. // A value of 0deg leaves the input unchanged. Implementations must not normalize this value in order to allow animations beyond 360deg.
apply_color_filter(Gfx::HueRotateFilter { hue_rotate.angle_degrees() }); apply_color_filter(Gfx::HueRotateFilter { hue_rotate.angle_degrees });
}, },
[&](CSS::Filter::DropShadow const&) { [&](CSS::ResolvedBackdropFilter::DropShadow const&) {
dbgln("TODO: Implement drop-shadow() filter function!"); dbgln("TODO: Implement drop-shadow() filter function!");
}); });
} }
} }
void apply_backdrop_filter(PaintContext& context, Layout::Node const& node, CSSPixelRect const& backdrop_rect, BorderRadiiData const& border_radii_data, CSS::BackdropFilter const& backdrop_filter) void apply_backdrop_filter(PaintContext& context, CSSPixelRect const& backdrop_rect, BorderRadiiData const& border_radii_data, CSS::ResolvedBackdropFilter const& backdrop_filter)
{ {
// This performs the backdrop filter operation: https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation // This performs the backdrop filter operation: https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation
@ -121,7 +121,7 @@ void apply_backdrop_filter(PaintContext& context, Layout::Node const& node, CSSP
} }
auto backdrop_bitmap = maybe_backdrop_bitmap.release_value(); auto backdrop_bitmap = maybe_backdrop_bitmap.release_value();
// 2. Apply the backdrop-filters filter operations to the entire contents of T'. // 2. Apply the backdrop-filters filter operations to the entire contents of T'.
apply_filter_list(*backdrop_bitmap, node, backdrop_filter.filters()); apply_filter_list(*backdrop_bitmap, backdrop_filter.filters);
// FIXME: 3. If element B has any transforms (between B and the Backdrop Root), apply the inverse of those transforms to the contents of T. // FIXME: 3. If element B has any transforms (between B and the Backdrop Root), apply the inverse of those transforms to the contents of T.

View file

@ -12,8 +12,8 @@
namespace Web::Painting { namespace Web::Painting {
void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, ReadonlySpan<CSS::FilterFunction> filter_list); void apply_filter_list(Gfx::Bitmap& target_bitmap, ReadonlySpan<CSS::ResolvedBackdropFilter::FilterFunction> filter_list);
void apply_backdrop_filter(PaintContext&, Layout::Node const&, CSSPixelRect const&, BorderRadiiData const&, CSS::BackdropFilter const&); void apply_backdrop_filter(PaintContext&, CSSPixelRect const&, BorderRadiiData const&, CSS::ResolvedBackdropFilter const&);
} }

View file

@ -325,7 +325,7 @@ void PaintableBox::paint_backdrop_filter(PaintContext& context) const
{ {
auto& backdrop_filter = computed_values().backdrop_filter(); auto& backdrop_filter = computed_values().backdrop_filter();
if (!backdrop_filter.is_none()) if (!backdrop_filter.is_none())
apply_backdrop_filter(context, layout_node(), absolute_border_box_rect(), normalized_border_radii_data(), backdrop_filter); apply_backdrop_filter(context, absolute_border_box_rect(), normalized_border_radii_data(), backdrop_filter);
} }
void PaintableBox::paint_background(PaintContext& context) const void PaintableBox::paint_background(PaintContext& context) const