/src/serenity/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibWeb/Layout/ImageBox.h> |
8 | | #include <LibWeb/Layout/SVGClipBox.h> |
9 | | #include <LibWeb/Layout/SVGMaskBox.h> |
10 | | #include <LibWeb/Painting/DisplayListPlayerCPU.h> |
11 | | #include <LibWeb/Painting/SVGClipPaintable.h> |
12 | | #include <LibWeb/Painting/SVGGraphicsPaintable.h> |
13 | | #include <LibWeb/Painting/StackingContext.h> |
14 | | #include <LibWeb/SVG/SVGSVGElement.h> |
15 | | |
16 | | namespace Web::Painting { |
17 | | |
18 | | template<typename T> |
19 | | static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element) |
20 | 0 | { |
21 | 0 | return graphics_element.layout_node()->first_child_of_type<T>(); |
22 | 0 | } Unexecuted instantiation: SVGMaskable.cpp:Web::Layout::SVGMaskBox const* Web::Painting::first_child_layout_node_of_type<Web::Layout::SVGMaskBox>(Web::SVG::SVGGraphicsElement const&) Unexecuted instantiation: SVGMaskable.cpp:Web::Layout::SVGClipBox const* Web::Painting::first_child_layout_node_of_type<Web::Layout::SVGClipBox>(Web::SVG::SVGGraphicsElement const&) |
23 | | |
24 | | static auto get_mask_box(SVG::SVGGraphicsElement const& graphics_element) |
25 | 0 | { |
26 | 0 | return first_child_layout_node_of_type<Layout::SVGMaskBox>(graphics_element); |
27 | 0 | } |
28 | | |
29 | | static auto get_clip_box(SVG::SVGGraphicsElement const& graphics_element) |
30 | 0 | { |
31 | 0 | return first_child_layout_node_of_type<Layout::SVGClipBox>(graphics_element); |
32 | 0 | } |
33 | | |
34 | | Optional<CSSPixelRect> SVGMaskable::get_masking_area_of_svg() const |
35 | 0 | { |
36 | 0 | auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg()); |
37 | 0 | Optional<CSSPixelRect> masking_area = {}; |
38 | 0 | if (auto* mask_box = get_mask_box(graphics_element)) { |
39 | 0 | masking_area = mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); |
40 | 0 | } |
41 | 0 | if (auto* clip_box = get_clip_box(graphics_element)) { |
42 | | // 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. |
43 | 0 | auto clip_area = clip_box->paintable_box()->absolute_border_box_rect(); |
44 | 0 | if (masking_area.has_value()) |
45 | 0 | masking_area = masking_area->intersected(clip_area); |
46 | 0 | else |
47 | 0 | masking_area = clip_area; |
48 | 0 | } |
49 | 0 | return masking_area; |
50 | 0 | } |
51 | | |
52 | | static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) |
53 | 0 | { |
54 | 0 | switch (mask_type) { |
55 | 0 | case CSS::MaskType::Alpha: |
56 | 0 | return Gfx::Bitmap::MaskKind::Alpha; |
57 | 0 | case CSS::MaskType::Luminance: |
58 | 0 | return Gfx::Bitmap::MaskKind::Luminance; |
59 | 0 | default: |
60 | 0 | VERIFY_NOT_REACHED(); |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | | Optional<Gfx::Bitmap::MaskKind> SVGMaskable::get_mask_type_of_svg() const |
65 | 0 | { |
66 | 0 | auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg()); |
67 | 0 | if (auto* mask_box = get_mask_box(graphics_element)) |
68 | 0 | return mask_type_to_gfx_mask_kind(mask_box->computed_values().mask_type()); |
69 | 0 | if (get_clip_box(graphics_element)) |
70 | 0 | return Gfx::Bitmap::MaskKind::Alpha; |
71 | 0 | return {}; |
72 | 0 | } |
73 | | |
74 | | RefPtr<Gfx::Bitmap> SVGMaskable::calculate_mask_of_svg(PaintContext& context, CSSPixelRect const& masking_area) const |
75 | 0 | { |
76 | 0 | auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg()); |
77 | 0 | auto mask_rect = context.enclosing_device_rect(masking_area); |
78 | 0 | auto paint_mask_or_clip = [&](PaintableBox const& paintable) -> RefPtr<Gfx::Bitmap> { |
79 | 0 | auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>()); |
80 | 0 | RefPtr<Gfx::Bitmap> mask_bitmap = {}; |
81 | 0 | if (mask_bitmap_or_error.is_error()) |
82 | 0 | return {}; |
83 | 0 | mask_bitmap = mask_bitmap_or_error.release_value(); |
84 | 0 | auto display_list = DisplayList::create(); |
85 | 0 | DisplayListRecorder display_list_recorder(*display_list); |
86 | 0 | display_list_recorder.translate(-mask_rect.location().to_type<int>()); |
87 | 0 | auto paint_context = context.clone(display_list_recorder); |
88 | 0 | paint_context.set_svg_transform(graphics_element.get_transform()); |
89 | 0 | paint_context.set_draw_svg_geometry_for_clip_path(is<SVGClipPaintable>(paintable)); |
90 | 0 | StackingContext::paint_node_as_stacking_context(paintable, paint_context); |
91 | 0 | DisplayListPlayerCPU display_list_player { *mask_bitmap }; |
92 | 0 | display_list_player.execute(display_list); |
93 | 0 | return mask_bitmap; |
94 | 0 | }; |
95 | 0 | RefPtr<Gfx::Bitmap> mask_bitmap = {}; |
96 | 0 | if (auto* mask_box = get_mask_box(graphics_element)) { |
97 | 0 | auto& mask_paintable = static_cast<PaintableBox const&>(*mask_box->paintable()); |
98 | 0 | mask_bitmap = paint_mask_or_clip(mask_paintable); |
99 | 0 | } |
100 | 0 | if (auto* clip_box = get_clip_box(graphics_element)) { |
101 | 0 | auto& clip_paintable = static_cast<PaintableBox const&>(*clip_box->paintable()); |
102 | 0 | auto clip_bitmap = paint_mask_or_clip(clip_paintable); |
103 | | // Combine the clip-path with the mask (if present). |
104 | 0 | if (mask_bitmap && clip_bitmap) |
105 | 0 | mask_bitmap->apply_mask(*clip_bitmap, Gfx::Bitmap::MaskKind::Alpha); |
106 | 0 | if (!mask_bitmap) |
107 | 0 | mask_bitmap = clip_bitmap; |
108 | 0 | } |
109 | 0 | return mask_bitmap; |
110 | 0 | } |
111 | | |
112 | | } |