From acd53697748f135be184c9cfec515a7c837e001e Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 25 Apr 2024 20:08:40 +0200 Subject: [PATCH] LibWeb: Separate svg mask calculation into SVGMaskable Preparation work before adding support for SVGForeignObjectElement masking. Having a mixin makes possible sharing mask calculation between SVGForeignObjectElement's paintable and SVGGraphicsPaintable. Both has to support masking: - PaintableWithLines (from SVGForeignObjectElement) -> PaintableBox - SVGGraphicsPaintable -> SVGPaintable -> PaintableBox After this change it will be possible to introduce a new paintable type for foreignObject that inherits from both PaintableWithLines and SVGMaskable. --- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/Painting/SVGGraphicsPaintable.cpp | 79 --------------- .../LibWeb/Painting/SVGGraphicsPaintable.h | 11 ++- .../Libraries/LibWeb/Painting/SVGMaskable.cpp | 96 +++++++++++++++++++ .../Libraries/LibWeb/Painting/SVGMaskable.h | 25 +++++ 5 files changed, 129 insertions(+), 83 deletions(-) create mode 100644 Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp create mode 100644 Userland/Libraries/LibWeb/Painting/SVGMaskable.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index a9eecec0ca4..5169b399eb8 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -543,6 +543,7 @@ set(SOURCES Painting/SVGPathPaintable.cpp Painting/SVGGraphicsPaintable.cpp Painting/SVGMaskPaintable.cpp + Painting/SVGMaskable.cpp Painting/SVGClipPaintable.cpp Painting/SVGPaintable.cpp Painting/SVGSVGPaintable.cpp diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index 0a6f91ec708..6a955163a2d 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -7,11 +7,9 @@ #include #include #include -#include #include #include #include -#include #include namespace Web::Painting { @@ -31,81 +29,4 @@ Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const return static_cast(layout_node()); } -Optional SVGGraphicsPaintable::get_masking_area() const -{ - auto const& graphics_element = verify_cast(*dom_node()); - Optional masking_area = {}; - if (auto* mask_box = graphics_element.layout_node()->first_child_of_type()) - masking_area = mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); - if (auto* clip_box = graphics_element.layout_node()->first_child_of_type()) { - // This is a bit ad-hoc, but if we have both a mask and a clip-path, intersect the two areas to find the masking area. - auto clip_area = clip_box->paintable_box()->absolute_border_box_rect(); - if (masking_area.has_value()) - masking_area = masking_area->intersected(clip_area); - else - masking_area = clip_area; - } - return masking_area; -} - -static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) -{ - switch (mask_type) { - case CSS::MaskType::Alpha: - return Gfx::Bitmap::MaskKind::Alpha; - case CSS::MaskType::Luminance: - return Gfx::Bitmap::MaskKind::Luminance; - default: - VERIFY_NOT_REACHED(); - } -} - -Optional SVGGraphicsPaintable::get_mask_type() const -{ - auto const& graphics_element = verify_cast(*dom_node()); - if (auto mask = graphics_element.mask()) - return mask_type_to_gfx_mask_kind(mask->layout_node()->computed_values().mask_type()); - if (graphics_element.clip_path()) - return Gfx::Bitmap::MaskKind::Alpha; - return {}; -} - -RefPtr SVGGraphicsPaintable::calculate_mask(PaintContext& context, CSSPixelRect const& masking_area) const -{ - auto const& graphics_element = verify_cast(*dom_node()); - auto mask_rect = context.enclosing_device_rect(masking_area); - auto paint_mask_or_clip = [&](PaintableBox const& paintable) -> RefPtr { - auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type()); - RefPtr mask_bitmap = {}; - if (mask_bitmap_or_error.is_error()) - return {}; - mask_bitmap = mask_bitmap_or_error.release_value(); - CommandList painting_commands; - RecordingPainter recording_painter(painting_commands); - recording_painter.translate(-mask_rect.location().to_type()); - auto paint_context = context.clone(recording_painter); - paint_context.set_svg_transform(graphics_element.get_transform()); - paint_context.set_draw_svg_geometry_for_clip_path(is(paintable)); - StackingContext::paint_node_as_stacking_context(paintable, paint_context); - CommandExecutorCPU executor { *mask_bitmap }; - painting_commands.execute(executor); - return mask_bitmap; - }; - RefPtr mask_bitmap = {}; - if (auto* mask_box = graphics_element.layout_node()->first_child_of_type()) { - auto& mask_paintable = static_cast(*mask_box->paintable()); - mask_bitmap = paint_mask_or_clip(mask_paintable); - } - if (auto* clip_box = graphics_element.layout_node()->first_child_of_type()) { - auto& clip_paintable = static_cast(*clip_box->paintable()); - auto clip_bitmap = paint_mask_or_clip(clip_paintable); - // Combine the clip-path with the mask (if present). - if (mask_bitmap && clip_bitmap) - mask_bitmap->apply_mask(*clip_bitmap, Gfx::Bitmap::MaskKind::Alpha); - if (!mask_bitmap) - mask_bitmap = clip_bitmap; - } - return mask_bitmap; -} - } diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h index e4ea76db5b1..d1da11e607b 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h @@ -7,11 +7,13 @@ #pragma once #include +#include #include namespace Web::Painting { -class SVGGraphicsPaintable : public SVGPaintable { +class SVGGraphicsPaintable : public SVGPaintable + , public SVGMaskable { JS_CELL(SVGGraphicsPaintable, SVGPaintable); public: @@ -49,9 +51,10 @@ public: Layout::SVGGraphicsBox const& layout_box() const; - virtual Optional get_masking_area() const override; - virtual Optional get_mask_type() const override; - virtual RefPtr calculate_mask(PaintContext&, CSSPixelRect const& masking_area) const override; + virtual JS::GCPtr dom_node_of_svg() const override { return dom_node(); } + virtual Optional get_masking_area() const override { return get_masking_area_of_svg(); } + virtual Optional get_mask_type() const override { return get_mask_type_of_svg(); } + virtual RefPtr calculate_mask(PaintContext& paint_context, CSSPixelRect const& masking_area) const override { return calculate_mask_of_svg(paint_context, masking_area); } void set_computed_transforms(ComputedTransforms computed_transforms) { diff --git a/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp b/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp new file mode 100644 index 00000000000..978673758cb --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +Optional SVGMaskable::get_masking_area_of_svg() const +{ + auto const& graphics_element = verify_cast(*dom_node_of_svg()); + Optional masking_area = {}; + if (auto* mask_box = graphics_element.layout_node()->first_child_of_type()) { + masking_area = mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); + } + if (auto* clip_box = graphics_element.layout_node()->first_child_of_type()) { + // This is a bit ad-hoc, but if we have both a mask and a clip-path, intersect the two areas to find the masking area. + auto clip_area = clip_box->paintable_box()->absolute_border_box_rect(); + if (masking_area.has_value()) + masking_area = masking_area->intersected(clip_area); + else + masking_area = clip_area; + } + return masking_area; +} + +static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) +{ + switch (mask_type) { + case CSS::MaskType::Alpha: + return Gfx::Bitmap::MaskKind::Alpha; + case CSS::MaskType::Luminance: + return Gfx::Bitmap::MaskKind::Luminance; + default: + VERIFY_NOT_REACHED(); + } +} + +Optional SVGMaskable::get_mask_type_of_svg() const +{ + auto const& graphics_element = verify_cast(*dom_node_of_svg()); + if (auto mask = graphics_element.mask()) + return mask_type_to_gfx_mask_kind(mask->layout_node()->computed_values().mask_type()); + if (graphics_element.clip_path()) + return Gfx::Bitmap::MaskKind::Alpha; + return {}; +} + +RefPtr SVGMaskable::calculate_mask_of_svg(PaintContext& context, CSSPixelRect const& masking_area) const +{ + auto const& graphics_element = verify_cast(*dom_node_of_svg()); + auto mask_rect = context.enclosing_device_rect(masking_area); + auto paint_mask_or_clip = [&](PaintableBox const& paintable) -> RefPtr { + auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type()); + RefPtr mask_bitmap = {}; + if (mask_bitmap_or_error.is_error()) + return {}; + mask_bitmap = mask_bitmap_or_error.release_value(); + CommandList painting_commands; + RecordingPainter recording_painter(painting_commands); + recording_painter.translate(-mask_rect.location().to_type()); + auto paint_context = context.clone(recording_painter); + paint_context.set_svg_transform(graphics_element.get_transform()); + paint_context.set_draw_svg_geometry_for_clip_path(is(paintable)); + StackingContext::paint_node_as_stacking_context(paintable, paint_context); + CommandExecutorCPU executor { *mask_bitmap }; + painting_commands.execute(executor); + return mask_bitmap; + }; + RefPtr mask_bitmap = {}; + if (auto* mask_box = graphics_element.layout_node()->first_child_of_type()) { + auto& mask_paintable = static_cast(*mask_box->paintable()); + mask_bitmap = paint_mask_or_clip(mask_paintable); + } + if (auto* clip_box = graphics_element.layout_node()->first_child_of_type()) { + auto& clip_paintable = static_cast(*clip_box->paintable()); + auto clip_bitmap = paint_mask_or_clip(clip_paintable); + // Combine the clip-path with the mask (if present). + if (mask_bitmap && clip_bitmap) + mask_bitmap->apply_mask(*clip_bitmap, Gfx::Bitmap::MaskKind::Alpha); + if (!mask_bitmap) + mask_bitmap = clip_bitmap; + } + return mask_bitmap; +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGMaskable.h b/Userland/Libraries/LibWeb/Painting/SVGMaskable.h new file mode 100644 index 00000000000..4a4628cf6f2 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/SVGMaskable.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#pragma once + +namespace Web::Painting { + +class SVGMaskable { +public: + virtual ~SVGMaskable() = default; + + virtual JS::GCPtr dom_node_of_svg() const = 0; + + Optional get_masking_area_of_svg() const; + Optional get_mask_type_of_svg() const; + RefPtr calculate_mask_of_svg(PaintContext&, CSSPixelRect const& masking_area) const; +}; + +}