diff options
author | MacDue <macdue@dueutil.tech> | 2022-09-15 08:31:32 +0100 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2022-09-16 10:50:48 +0100 |
commit | 011439d3e3b37503ea7b4e36437217d9a587bed2 (patch) | |
tree | 6f09d93d1b937d04265ae987bc29a6dbbf2be681 | |
parent | 60356c8ddeb1916cac0172f17b4fcf1c2f17dae0 (diff) | |
download | serenity-011439d3e3b37503ea7b4e36437217d9a587bed2.zip |
LibWeb: Paint `backdrop-filter` effects!
This implements all the filters other than `saturate()`,
`hue-rotate()`, and `drop-shadow()`.
There are still a lot of FIXMEs to handle in the actual implementation
though, particularly around supporting transforms, but this handles
the most common use cases :^)
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/FilterPainting.cpp | 129 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/FilterPainting.h | 19 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/PaintableBox.cpp | 9 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/PaintableBox.h | 1 |
5 files changed, 159 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f297d213b8..2037d55dfa 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -335,6 +335,7 @@ set(SOURCES Painting/CanvasPaintable.cpp Painting/CheckBoxPaintable.cpp Painting/GradientPainting.cpp + Painting/FilterPainting.cpp Painting/ImagePaintable.cpp Painting/InlinePaintable.cpp Painting/LabelablePaintable.cpp diff --git a/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp b/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp new file mode 100644 index 0000000000..639349d433 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022, MacDue <macdue@dueutil.tech> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/Filters/BrightnessFilter.h> +#include <LibGfx/Filters/ContrastFilter.h> +#include <LibGfx/Filters/GrayscaleFilter.h> +#include <LibGfx/Filters/InvertFilter.h> +#include <LibGfx/Filters/OpacityFilter.h> +#include <LibGfx/Filters/SepiaFilter.h> +#include <LibGfx/Filters/StackBlurFilter.h> +#include <LibWeb/Layout/Node.h> +#include <LibWeb/Painting/BorderRadiusCornerClipper.h> +#include <LibWeb/Painting/FilterPainting.h> + +namespace Web::Painting { + +void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, Span<CSS::FilterFunction const> filter_list) +{ + for (auto& filter_function : filter_list) { + // See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions + filter_function.visit( + [&](CSS::Filter::Blur const& blur) { + // Applies a Gaussian blur to the input image. + // The passed parameter defines the value of the standard deviation to the Gaussian function. + Gfx::StackBlurFilter filter { target_bitmap }; + filter.process_rgba(blur.resolved_radius(node), Color::Transparent); + }, + [&](CSS::Filter::Color const& color) { + auto amount = color.resolved_amount(); + auto amount_clamped = clamp(amount, 0.0f, 1.0f); + auto apply_color_filter = [&](Gfx::ColorFilter const& filter) { + const_cast<Gfx::ColorFilter&>(filter).apply(target_bitmap, target_bitmap.rect(), target_bitmap, target_bitmap.rect()); + }; + switch (color.operation) { + case CSS::Filter::Color::Operation::Grayscale: { + // Converts the input image to grayscale. The passed parameter defines the proportion of the conversion. + // A value of 100% is completely grayscale. A value of 0% leaves the input unchanged. + apply_color_filter(Gfx::GrayscaleFilter { amount_clamped }); + break; + } + case CSS::Filter::Color::Operation::Brightness: { + // Applies a linear multiplier to input image, making it appear more or less bright. + // A value of 0% will create an image that is completely black. A value of 100% leaves the input unchanged. + // Values of amount over 100% are allowed, providing brighter results. + apply_color_filter(Gfx::BrightnessFilter { amount }); + break; + } + case CSS::Filter::Color::Operation::Contrast: { + // Adjusts the contrast of the input. A value of 0% will create an image that is completely gray. + // A value of 100% leaves the input unchanged. Values of amount over 100% are allowed, providing results with more contrast. + apply_color_filter(Gfx::ContrastFilter { amount }); + break; + } + case CSS::Filter::Color::Operation::Invert: { + // Inverts the samples in the input image. The passed parameter defines the proportion of the conversion. + // A value of 100% is completely inverted. A value of 0% leaves the input unchanged. + apply_color_filter(Gfx::InvertFilter { amount_clamped }); + break; + } + case CSS::Filter::Color::Operation::Opacity: { + // Applies transparency to the samples in the input image. The passed parameter defines the proportion of the conversion. + // A value of 0% is completely transparent. A value of 100% leaves the input unchanged. + apply_color_filter(Gfx::OpacityFilter { amount_clamped }); + break; + } + case CSS::Filter::Color::Operation::Sepia: { + // Converts the input image to sepia. The passed parameter defines the proportion of the conversion. + // A value of 100% is completely sepia. A value of 0% leaves the input unchanged. + apply_color_filter(Gfx::SepiaFilter { amount_clamped }); + break; + } + case CSS::Filter::Color::Operation::Saturate: { + dbgln("TODO: Implement saturate() filter function!"); + break; + } + default: + break; + } + }, + [&](CSS::Filter::HueRotate const&) { + dbgln("TODO: Implement hue-rotate() filter function!"); + }, + [&](CSS::Filter::DropShadow const&) { + dbgln("TODO: Implement drop-shadow() filter function!"); + }); + } +} + +void apply_backdrop_filter(PaintContext& context, Layout::Node const& node, Gfx::FloatRect const& backdrop_rect, BorderRadiiData const& border_radii_data, CSS::BackdropFilter const& backdrop_filter) +{ + // This performs the backdrop filter operation: https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation + + auto backdrop_region = backdrop_rect.to_rounded<int>(); + + // Note: The region bitmap can be smaller than the backdrop_region if it's at the edge of canvas. + Gfx::IntRect actual_region {}; + + // FIXME: Go through the steps to find the "Backdrop Root Image" + // https://drafts.fxtf.org/filter-effects-2/#BackdropRoot + + // 1. Copy the Backdrop Root Image into a temporary buffer, such as a raster image. Call this buffer T’. + auto maybe_backdrop_bitmap = context.painter().get_region_bitmap(backdrop_region, Gfx::BitmapFormat::BGRA8888, actual_region); + if (actual_region.is_empty()) + return; + if (maybe_backdrop_bitmap.is_error()) { + dbgln("Failed get region bitmap for backdrop-filter"); + return; + } + auto backdrop_bitmap = maybe_backdrop_bitmap.release_value(); + // 2. Apply the backdrop-filter’s filter operations to the entire contents of T'. + apply_filter_list(*backdrop_bitmap, node, 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’. + + // 4. Apply a clip to the contents of T’, using the border box of element B, including border-radius if specified. Note that the children of B are not considered for the sizing or location of this clip. + ScopedCornerRadiusClip corner_clipper { context.painter(), backdrop_region, border_radii_data }; + + // FIXME: 5. Draw all of element B, including its background, border, and any children elements, into T’. + + // FXIME: 6. If element B has any transforms, effects, or clips, apply those to T’. + + // 7. Composite the contents of T’ into element B’s parent, using source-over compositing. + context.painter().blit(actual_region.location(), *backdrop_bitmap, backdrop_bitmap->rect()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/FilterPainting.h b/Userland/Libraries/LibWeb/Painting/FilterPainting.h new file mode 100644 index 0000000000..d4b3e693b6 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/FilterPainting.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022, MacDue <macdue@dueutil.tech> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibGfx/Forward.h> +#include <LibWeb/CSS/BackdropFilter.h> +#include <LibWeb/Forward.h> + +namespace Web::Painting { + +void apply_filter_list(Gfx::Bitmap& target_bitmap, Layout::Node const& node, Span<CSS::FilterFunction const> filter_list); + +void apply_backdrop_filter(PaintContext&, Layout::Node const&, Gfx::FloatRect const&, BorderRadiiData const&, CSS::BackdropFilter const&); + +} diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 97add1cf2f..58d6e397f7 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -11,6 +11,7 @@ #include <LibWeb/Layout/BlockContainer.h> #include <LibWeb/Layout/InitialContainingBlock.h> #include <LibWeb/Painting/BackgroundPainting.h> +#include <LibWeb/Painting/FilterPainting.h> #include <LibWeb/Painting/PaintableBox.h> #include <LibWeb/Painting/ShadowPainting.h> #include <LibWeb/Painting/StackingContext.h> @@ -122,6 +123,7 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const auto border_box = absolute_border_box_rect(); context.painter().add_clip_rect(clip_rect.to_rect().resolved(Paintable::layout_node(), border_box).to_rounded<int>()); } + paint_backdrop_filter(context); paint_background(context); paint_box_shadow(context); } @@ -193,6 +195,13 @@ void PaintableBox::paint_border(PaintContext& context) const paint_all_borders(context, absolute_border_box_rect(), normalized_border_radii_data(), borders_data); } +void PaintableBox::paint_backdrop_filter(PaintContext& context) const +{ + auto& backdrop_filter = computed_values().backdrop_filter(); + if (!backdrop_filter.is_none()) + Painting::apply_backdrop_filter(context, layout_node(), absolute_border_box_rect(), normalized_border_radii_data(), backdrop_filter); +} + void PaintableBox::paint_background(PaintContext& context) const { // If the body's background properties were propagated to the root element, do no re-paint the body's background. diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index ccd917c4b1..e19c476173 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -118,6 +118,7 @@ protected: explicit PaintableBox(Layout::Box const&); virtual void paint_border(PaintContext&) const; + virtual void paint_backdrop_filter(PaintContext&) const; virtual void paint_background(PaintContext&) const; virtual void paint_box_shadow(PaintContext&) const; |