From 980c92e9b542f3b0044744aabd9a5d94be7c8356 Mon Sep 17 00:00:00 2001 From: MacDue Date: Thu, 15 Sep 2022 08:31:14 +0100 Subject: [PATCH] LibWeb: Add FilterValueListStyleValue This style value holds a list of CSS filter function calls e.g. blur(10px) invert() grayscale() It will be used to implement backdrop-filter, but the same style value can be used for the image filter property. (The name is a little awkward but it's referenced to as filter-value-list in the spec too). --- Userland/Libraries/LibWeb/CSS/StyleValue.cpp | 171 +++++++++++++++++++ Userland/Libraries/LibWeb/CSS/StyleValue.h | 77 +++++++++ Userland/Libraries/LibWeb/Forward.h | 1 + 3 files changed, 249 insertions(+) diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index e8c33876bb..31330f3987 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -96,6 +96,12 @@ ContentStyleValue const& StyleValue::as_content() const return static_cast(*this); } +FilterValueListStyleValue const& StyleValue::as_filter_value_list() const +{ + VERIFY(is_filter_value_list()); + return static_cast(*this); +} + FlexStyleValue const& StyleValue::as_flex() const { VERIFY(is_flex()); @@ -1163,6 +1169,171 @@ bool ContentStyleValue::equals(StyleValue const& other) const return true; } +float Filter::Blur::resolved_radius(Layout::Node const& node) const +{ + // Default value when omitted is 0px. + auto sigma = 0; + if (radius.has_value()) + sigma = radius->resolved(node).to_px(node); + // Note: The radius/sigma of the blur needs to be doubled for LibGfx's blur functions. + 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.resolved(node).to_px(node), + offset_y.resolved(node).to_px(node), + radius.has_value() ? radius->resolved(node).to_px(node) : 0.0f, + color.has_value() ? *color : node.computed_values().color() + }; +} + +float Filter::HueRotate::angle_degrees() const +{ + // Default value when omitted is 0deg. + if (!angle.has_value()) + return 0.0f; + return angle->visit([&](Angle const& angle) { return angle.to_degrees(); }, [&](auto) { return 0.0f; }); +} + +float Filter::Color::resolved_amount() const +{ + if (amount.has_value()) { + if (amount->is_percentage()) + return amount->percentage().as_fraction(); + return amount->number().value(); + } + // All color filters (brightness, sepia, etc) have a default amount of 1. + return 1.0f; +} + +String FilterValueListStyleValue::to_string() const +{ + StringBuilder builder {}; + bool first = true; + for (auto& filter_function : filter_value_list()) { + if (!first) + builder.append(' '); + filter_function.visit( + [&](Filter::Blur const& blur) { + builder.append("blur("sv); + if (blur.radius.has_value()) + builder.append(blur.radius->to_string()); + }, + [&](Filter::DropShadow const& drop_shadow) { + builder.appendff("drop-shadow({} {}"sv, + drop_shadow.offset_x, drop_shadow.offset_y); + if (drop_shadow.radius.has_value()) + builder.appendff(" {}", drop_shadow.radius->to_string()); + if (drop_shadow.color.has_value()) { + builder.append(' '); + serialize_a_srgb_value(builder, *drop_shadow.color); + } + }, + [&](Filter::HueRotate const& hue_rotate) { + builder.append("hue-rotate("sv); + if (hue_rotate.angle.has_value()) { + hue_rotate.angle->visit( + [&](Angle const& angle) { + builder.append(angle.to_string()); + }, + [&](auto&) { + builder.append('0'); + }); + } + }, + [&](Filter::Color const& color) { + builder.appendff("{}(", + [&] { + switch (color.operation) { + case Filter::Color::Operation::Brightness: + return "brightness"sv; + case Filter::Color::Operation::Contrast: + return "contrast"sv; + case Filter::Color::Operation::Grayscale: + return "grayscale"sv; + case Filter::Color::Operation::Invert: + return "invert"sv; + case Filter::Color::Operation::Opacity: + return "opacity"sv; + case Filter::Color::Operation::Saturate: + return "saturate"sv; + case Filter::Color::Operation::Sepia: + return "sepia"sv; + default: + VERIFY_NOT_REACHED(); + } + }()); + if (color.amount.has_value()) + builder.append(color.amount->to_string()); + }); + builder.append(')'); + first = false; + } + return builder.to_string(); +} + +static bool operator==(Filter::Blur const& a, Filter::Blur const& b) +{ + return a.radius == b.radius; +} + +static bool operator==(Filter::DropShadow const& a, Filter::DropShadow const& b) +{ + return a.offset_x == b.offset_x && a.offset_y == b.offset_y && a.radius == b.radius && a.color == b.color; +} + +static bool operator==(Filter::HueRotate::Zero const&, Filter::HueRotate::Zero const&) +{ + return true; +} + +static bool operator==(Filter::Color const& a, Filter::Color const& b) +{ + return a.operation == b.operation && a.amount == b.amount; +} + +static bool operator==(Filter::HueRotate const& a, Filter::HueRotate const& b) +{ + return a.angle == b.angle; +} + +static bool variant_equals(auto const& a, auto const& b) +{ + return a.visit([&](auto const& held_value) { + using HeldType = AK::Detail::Decay; + bool other_holds_same_type = b.template has(); + return other_holds_same_type && held_value == b.template get(); + }); +} + +static bool operator==(Filter::HueRotate::AngleOrZero const& a, Filter::HueRotate::AngleOrZero const& b) +{ + return variant_equals(a, b); +} + +static bool operator==(FilterFunction const& a, FilterFunction const& b) +{ + return variant_equals(a, b); +} + +bool FilterValueListStyleValue::equals(StyleValue const& other) const +{ + if (type() != other.type()) + return false; + auto const& typed_other = other.as_filter_value_list(); + if (m_filter_value_list.size() != typed_other.m_filter_value_list.size()) + return false; + for (size_t i = 0; i < m_filter_value_list.size(); i++) { + if (m_filter_value_list[i] != typed_other.m_filter_value_list[i]) + return false; + } + return true; +} + String FlexStyleValue::to_string() const { return String::formatted("{} {} {}", m_grow->to_string(), m_shrink->to_string(), m_basis->to_string()); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index ccfe444dbc..3e5e0fb3ee 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -97,6 +97,52 @@ struct EdgeRect { Gfx::FloatRect resolved(Layout::Node const&, Gfx::FloatRect) const; }; +namespace Filter { + +struct Blur { + Optional radius {}; + float resolved_radius(Layout::Node const&) const; +}; + +struct DropShadow { + Length offset_x; + Length offset_y; + Optional radius {}; + Optional color {}; + struct Resolved { + float offset_x; + float offset_y; + float radius; + Color color; + }; + Resolved resolved(Layout::Node const&) const; +}; + +struct HueRotate { + struct Zero { }; + using AngleOrZero = Variant; + Optional angle {}; + float angle_degrees() const; +}; + +struct Color { + enum class Operation { + Brightness, + Contrast, + Grayscale, + Invert, + Opacity, + Saturate, + Sepia + } operation; + Optional amount {}; + float resolved_amount() const; +}; + +}; + +using FilterFunction = Variant; + // FIXME: Find a better place for this helper. inline Gfx::Painter::ScalingMode to_gfx_scaling_mode(CSS::ImageRendering css_value) { @@ -128,6 +174,7 @@ public: Calculated, Color, Content, + FilterValueList, Flex, FlexFlow, Font, @@ -172,6 +219,7 @@ public: bool is_calculated() const { return type() == Type::Calculated; } bool is_color() const { return type() == Type::Color; } bool is_content() const { return type() == Type::Content; } + bool is_filter_value_list() const { return type() == Type::FilterValueList; } bool is_flex() const { return type() == Type::Flex; } bool is_flex_flow() const { return type() == Type::FlexFlow; } bool is_font() const { return type() == Type::Font; } @@ -214,6 +262,7 @@ public: CalculatedStyleValue const& as_calculated() const; ColorStyleValue const& as_color() const; ContentStyleValue const& as_content() const; + FilterValueListStyleValue const& as_filter_value_list() const; FlexFlowStyleValue const& as_flex_flow() const; FlexStyleValue const& as_flex() const; FontStyleValue const& as_font() const; @@ -254,6 +303,7 @@ public: CalculatedStyleValue& as_calculated() { return const_cast(const_cast(*this).as_calculated()); } ColorStyleValue& as_color() { return const_cast(const_cast(*this).as_color()); } ContentStyleValue& as_content() { return const_cast(const_cast(*this).as_content()); } + FilterValueListStyleValue& as_filter_value_list() { return const_cast(const_cast(*this).as_filter_value_list()); } FlexFlowStyleValue& as_flex_flow() { return const_cast(const_cast(*this).as_flex_flow()); } FlexStyleValue& as_flex() { return const_cast(const_cast(*this).as_flex()); } FontStyleValue& as_font() { return const_cast(const_cast(*this).as_font()); } @@ -803,6 +853,33 @@ private: RefPtr m_alt_text; }; +class FilterValueListStyleValue final : public StyleValue { +public: + static NonnullRefPtr create( + Vector filter_value_list) + { + VERIFY(filter_value_list.size() >= 1); + return adopt_ref(*new FilterValueListStyleValue(move(filter_value_list))); + } + + Vector const& filter_value_list() const { return m_filter_value_list; } + + virtual String to_string() const override; + virtual bool equals(StyleValue const& other) const override; + + virtual ~FilterValueListStyleValue() override = default; + +private: + FilterValueListStyleValue(Vector filter_value_list) + : StyleValue(Type::FilterValueList) + , m_filter_value_list(move(filter_value_list)) + { + } + + // FIXME: No support for SVG filters yet + Vector m_filter_value_list; +}; + class FlexStyleValue final : public StyleValue { public: static NonnullRefPtr create( diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index c9ad260573..d6057d5bee 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -50,6 +50,7 @@ class CSSStyleSheet; class CSSSupportsRule; class Display; class ElementInlineCSSStyleDeclaration; +class FilterValueListStyleValue; class FlexFlowStyleValue; class FlexStyleValue; class FontFace;