/src/serenity/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibGfx/StylePainter.h> |
8 | | #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h> |
9 | | #include <LibWeb/CSS/StyleValues/LengthStyleValue.h> |
10 | | #include <LibWeb/CSS/StyleValues/PositionStyleValue.h> |
11 | | #include <LibWeb/HTML/DecodedImageData.h> |
12 | | #include <LibWeb/HTML/HTMLImageElement.h> |
13 | | #include <LibWeb/HTML/ImageRequest.h> |
14 | | #include <LibWeb/Painting/BorderRadiusCornerClipper.h> |
15 | | #include <LibWeb/Painting/ImagePaintable.h> |
16 | | #include <LibWeb/Platform/FontPlugin.h> |
17 | | |
18 | | namespace Web::Painting { |
19 | | |
20 | | JS_DEFINE_ALLOCATOR(ImagePaintable); |
21 | | |
22 | | JS::NonnullGCPtr<ImagePaintable> ImagePaintable::create(Layout::SVGImageBox const& layout_box) |
23 | 0 | { |
24 | 0 | return layout_box.heap().allocate_without_realm<ImagePaintable>(layout_box, layout_box.dom_node(), false, String {}, true); |
25 | 0 | } |
26 | | |
27 | | JS::NonnullGCPtr<ImagePaintable> ImagePaintable::create(Layout::ImageBox const& layout_box) |
28 | 0 | { |
29 | 0 | auto alt = layout_box.dom_node().get_attribute_value(HTML::AttributeNames::alt); |
30 | 0 | return layout_box.heap().allocate_without_realm<ImagePaintable>(layout_box, layout_box.image_provider(), layout_box.renders_as_alt_text(), move(alt), false); |
31 | 0 | } |
32 | | |
33 | | ImagePaintable::ImagePaintable(Layout::Box const& layout_box, Layout::ImageProvider const& image_provider, bool renders_as_alt_text, String alt_text, bool is_svg_image) |
34 | 0 | : PaintableBox(layout_box) |
35 | 0 | , m_renders_as_alt_text(renders_as_alt_text) |
36 | 0 | , m_alt_text(move(alt_text)) |
37 | 0 | , m_image_provider(image_provider) |
38 | 0 | , m_is_svg_image(is_svg_image) |
39 | 0 | { |
40 | 0 | const_cast<DOM::Document&>(layout_box.document()).register_viewport_client(*this); |
41 | 0 | } |
42 | | |
43 | | void ImagePaintable::visit_edges(JS::Cell::Visitor& visitor) |
44 | 0 | { |
45 | 0 | Base::visit_edges(visitor); |
46 | 0 | visitor.visit(m_image_provider.to_html_element()); |
47 | 0 | } |
48 | | |
49 | | void ImagePaintable::finalize() |
50 | 0 | { |
51 | 0 | Base::finalize(); |
52 | | |
53 | | // NOTE: We unregister from the document in finalize() to avoid trouble |
54 | | // in the scenario where our Document has already been swept by GC. |
55 | 0 | document().unregister_viewport_client(*this); |
56 | 0 | } |
57 | | |
58 | | void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const |
59 | 0 | { |
60 | 0 | if (!is_visible()) |
61 | 0 | return; |
62 | | |
63 | 0 | PaintableBox::paint(context, phase); |
64 | |
|
65 | 0 | if (phase == PaintPhase::Foreground) { |
66 | 0 | auto image_rect = context.rounded_device_rect(absolute_rect()); |
67 | 0 | if (m_renders_as_alt_text) { |
68 | 0 | auto enclosing_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>(); |
69 | 0 | context.display_list_recorder().draw_rect(enclosing_rect, Gfx::Color::Black); |
70 | 0 | context.display_list_recorder().draw_text(enclosing_rect, m_alt_text, Platform::FontPlugin::the().default_font(), Gfx::TextAlignment::Center, computed_values().color()); |
71 | 0 | } else if (auto bitmap = m_image_provider.current_image_bitmap(image_rect.size().to_type<int>())) { |
72 | 0 | ScopedCornerRadiusClip corner_clip { context, image_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; |
73 | 0 | auto image_int_rect = image_rect.to_type<int>(); |
74 | 0 | auto bitmap_rect = bitmap->rect(); |
75 | 0 | auto scaling_mode = to_gfx_scaling_mode(computed_values().image_rendering(), bitmap_rect, image_int_rect); |
76 | 0 | auto bitmap_aspect_ratio = (float)bitmap_rect.height() / bitmap_rect.width(); |
77 | 0 | auto image_aspect_ratio = (float)image_rect.height().value() / image_rect.width().value(); |
78 | |
|
79 | 0 | auto scale_x = 0.0f; |
80 | 0 | auto scale_y = 0.0f; |
81 | 0 | Gfx::IntRect bitmap_intersect = bitmap_rect; |
82 | |
|
83 | 0 | auto object_fit = m_is_svg_image ? CSS::ObjectFit::Contain : computed_values().object_fit(); |
84 | 0 | switch (object_fit) { |
85 | 0 | case CSS::ObjectFit::Fill: |
86 | 0 | scale_x = (float)image_int_rect.width() / bitmap_rect.width(); |
87 | 0 | scale_y = (float)image_int_rect.height() / bitmap_rect.height(); |
88 | 0 | break; |
89 | 0 | case CSS::ObjectFit::Contain: |
90 | 0 | if (bitmap_aspect_ratio >= image_aspect_ratio) { |
91 | 0 | scale_x = (float)image_int_rect.height() / bitmap_rect.height(); |
92 | 0 | scale_y = scale_x; |
93 | 0 | } else { |
94 | 0 | scale_x = (float)image_int_rect.width() / bitmap_rect.width(); |
95 | 0 | scale_y = scale_x; |
96 | 0 | } |
97 | 0 | break; |
98 | 0 | case CSS::ObjectFit::Cover: |
99 | 0 | if (bitmap_aspect_ratio >= image_aspect_ratio) { |
100 | 0 | scale_x = (float)image_int_rect.width() / bitmap_rect.width(); |
101 | 0 | scale_y = scale_x; |
102 | 0 | bitmap_intersect.set_height(bitmap_rect.width() * image_aspect_ratio); |
103 | 0 | } else { |
104 | 0 | scale_x = (float)image_int_rect.height() / bitmap_rect.height(); |
105 | 0 | scale_y = scale_x; |
106 | 0 | bitmap_intersect.set_width(bitmap_rect.height() / image_aspect_ratio); |
107 | 0 | } |
108 | 0 | break; |
109 | 0 | case CSS::ObjectFit::ScaleDown: |
110 | | // FIXME: Implement |
111 | 0 | case CSS::ObjectFit::None: |
112 | 0 | scale_x = 1; |
113 | 0 | scale_y = 1; |
114 | 0 | bitmap_intersect.set_size(image_int_rect.size()); |
115 | 0 | } |
116 | | |
117 | 0 | auto scaled_bitmap_width = bitmap_rect.width() * scale_x; |
118 | 0 | auto scaled_bitmap_height = bitmap_rect.height() * scale_y; |
119 | |
|
120 | 0 | auto residual_horizontal = CSSPixels::nearest_value_for(image_int_rect.width() - scaled_bitmap_width); |
121 | 0 | auto residual_vertical = CSSPixels::nearest_value_for(image_int_rect.height() - scaled_bitmap_height); |
122 | |
|
123 | 0 | bitmap_intersect.set_x((bitmap_rect.width() - bitmap_intersect.width()) / 2); |
124 | 0 | bitmap_intersect.set_y((bitmap_rect.height() - bitmap_intersect.height()) / 2); |
125 | |
|
126 | 0 | auto const& object_position = computed_values().object_position(); |
127 | |
|
128 | 0 | auto offset_x = 0; |
129 | 0 | if (object_position.edge_x == CSS::PositionEdge::Left) { |
130 | 0 | offset_x = object_position.offset_x.to_px(layout_node(), residual_horizontal).to_int(); |
131 | 0 | bitmap_intersect.set_x(0); |
132 | 0 | } else if (object_position.edge_x == CSS::PositionEdge::Right) { |
133 | 0 | offset_x = residual_horizontal.to_int() - object_position.offset_x.to_px(layout_node(), residual_horizontal).to_int(); |
134 | 0 | } |
135 | 0 | if (image_int_rect.width() < scaled_bitmap_width) |
136 | 0 | bitmap_intersect.set_x(-(offset_x / scale_x)); |
137 | |
|
138 | 0 | auto offset_y = 0; |
139 | 0 | if (object_position.edge_y == CSS::PositionEdge::Top) { |
140 | 0 | offset_y = object_position.offset_y.to_px(layout_node(), residual_vertical).to_int(); |
141 | 0 | bitmap_intersect.set_y(0); |
142 | 0 | } else if (object_position.edge_y == CSS::PositionEdge::Bottom) { |
143 | 0 | offset_y = residual_vertical.to_int() - object_position.offset_y.to_px(layout_node(), residual_vertical).to_int(); |
144 | 0 | } |
145 | 0 | if (image_int_rect.height() < scaled_bitmap_height) |
146 | 0 | bitmap_intersect.set_y(-(offset_y / scale_y)); |
147 | |
|
148 | 0 | Gfx::IntRect draw_rect = { |
149 | 0 | image_int_rect.x() + offset_x, |
150 | 0 | image_int_rect.y() + offset_y, |
151 | 0 | (int)scaled_bitmap_width, |
152 | 0 | (int)scaled_bitmap_height |
153 | 0 | }; |
154 | |
|
155 | 0 | context.display_list_recorder().draw_scaled_immutable_bitmap(draw_rect.intersected(image_int_rect), *bitmap, bitmap_rect.intersected(bitmap_intersect), scaling_mode); |
156 | 0 | } |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | void ImagePaintable::did_set_viewport_rect(CSSPixelRect const& viewport_rect) |
161 | 0 | { |
162 | 0 | const_cast<Layout::ImageProvider&>(m_image_provider).set_visible_in_viewport(viewport_rect.intersects(absolute_rect())); |
163 | 0 | } |
164 | | |
165 | | } |