/src/serenity/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> |
4 | | * Copyright (c) 2022, MacDue <macdue@dueutil.tech> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <LibGfx/AntiAliasingPainter.h> |
10 | | #include <LibGfx/Font/ScaledFont.h> |
11 | | #include <LibWeb/Layout/Node.h> |
12 | | #include <LibWeb/Layout/Viewport.h> |
13 | | #include <LibWeb/Painting/BackgroundPainting.h> |
14 | | #include <LibWeb/Painting/InlinePaintable.h> |
15 | | #include <LibWeb/Painting/PaintableBox.h> |
16 | | |
17 | | namespace Web::Painting { |
18 | | |
19 | | // https://drafts.csswg.org/css-images/#default-sizing |
20 | | static CSSPixelSize run_default_sizing_algorithm( |
21 | | Optional<CSSPixels> specified_width, Optional<CSSPixels> specified_height, |
22 | | Optional<CSSPixels> natural_width, Optional<CSSPixels> natural_height, |
23 | | Optional<CSSPixelFraction> natural_aspect_ratio, |
24 | | CSSPixelSize default_size) |
25 | 0 | { |
26 | | // If the specified size is a definite width and height, the concrete object size is given that width and height. |
27 | 0 | if (specified_width.has_value() && specified_height.has_value()) |
28 | 0 | return CSSPixelSize { specified_width.value(), specified_height.value() }; |
29 | | // If the specified size is only a width or height (but not both) then the concrete object size is given that specified width or height. |
30 | | // The other dimension is calculated as follows: |
31 | 0 | if (specified_width.has_value() || specified_height.has_value()) { |
32 | | // 1. If the object has a natural aspect ratio, |
33 | | // the missing dimension of the concrete object size is calculated using that aspect ratio and the present dimension. |
34 | 0 | if (natural_aspect_ratio.has_value() && !natural_aspect_ratio->might_be_saturated()) { |
35 | 0 | if (specified_width.has_value()) |
36 | 0 | return CSSPixelSize { specified_width.value(), (CSSPixels(1) / natural_aspect_ratio.value()) * specified_width.value() }; |
37 | 0 | if (specified_height.has_value()) |
38 | 0 | return CSSPixelSize { specified_height.value() * natural_aspect_ratio.value(), specified_height.value() }; |
39 | 0 | } |
40 | | // 2. Otherwise, if the missing dimension is present in the object’s natural dimensions, |
41 | | // the missing dimension is taken from the object’s natural dimensions. |
42 | 0 | if (specified_height.has_value() && natural_width.has_value()) |
43 | 0 | return CSSPixelSize { natural_width.value(), specified_height.value() }; |
44 | 0 | if (specified_width.has_value() && natural_height.has_value()) |
45 | 0 | return CSSPixelSize { specified_width.value(), natural_height.value() }; |
46 | | // 3. Otherwise, the missing dimension of the concrete object size is taken from the default object size. |
47 | 0 | if (specified_height.has_value()) |
48 | 0 | return CSSPixelSize { default_size.width(), specified_height.value() }; |
49 | 0 | if (specified_width.has_value()) |
50 | 0 | return CSSPixelSize { specified_width.value(), default_size.height() }; |
51 | 0 | VERIFY_NOT_REACHED(); |
52 | 0 | } |
53 | | // If the specified size has no constraints: |
54 | | // 1. If the object has a natural height or width, its size is resolved as if its natural dimensions were given as the specified size. |
55 | 0 | if (natural_width.has_value() || natural_height.has_value()) |
56 | 0 | return run_default_sizing_algorithm(natural_width, natural_height, natural_width, natural_height, natural_aspect_ratio, default_size); |
57 | | // FIXME: 2. Otherwise, its size is resolved as a contain constraint against the default object size. |
58 | 0 | return default_size; |
59 | 0 | } |
60 | | |
61 | | static RefPtr<DisplayList> compute_text_clip_paths(PaintContext& context, Paintable const& paintable) |
62 | 0 | { |
63 | 0 | auto text_clip_paths = DisplayList::create(); |
64 | 0 | DisplayListRecorder display_list_recorder(*text_clip_paths); |
65 | 0 | auto add_text_clip_path = [&](PaintableFragment const& fragment) { |
66 | 0 | auto glyph_run = fragment.glyph_run(); |
67 | 0 | if (!glyph_run || glyph_run->glyphs().is_empty()) |
68 | 0 | return; |
69 | | |
70 | 0 | auto fragment_absolute_rect = fragment.absolute_rect(); |
71 | 0 | auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect); |
72 | |
|
73 | 0 | DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) }; |
74 | 0 | auto scale = context.device_pixels_per_css_pixel(); |
75 | 0 | display_list_recorder.draw_text_run(baseline_start.to_type<int>(), *glyph_run, Gfx::Color::Black, fragment_absolute_device_rect.to_type<int>(), scale); |
76 | 0 | }; |
77 | |
|
78 | 0 | paintable.for_each_in_inclusive_subtree([&](auto& paintable) { |
79 | 0 | if (is<PaintableWithLines>(paintable)) { |
80 | 0 | auto const& paintable_lines = static_cast<PaintableWithLines const&>(paintable); |
81 | 0 | for (auto const& fragment : paintable_lines.fragments()) { |
82 | 0 | if (is<Layout::TextNode>(fragment.layout_node())) |
83 | 0 | add_text_clip_path(fragment); |
84 | 0 | } |
85 | 0 | } else if (is<InlinePaintable>(paintable)) { |
86 | 0 | auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable); |
87 | 0 | for (auto const& fragment : inline_paintable.fragments()) { |
88 | 0 | if (is<Layout::TextNode>(fragment.layout_node())) |
89 | 0 | add_text_clip_path(fragment); |
90 | 0 | } |
91 | 0 | } |
92 | 0 | return TraversalDecision::Continue; |
93 | 0 | }); |
94 | |
|
95 | 0 | return text_clip_paths; |
96 | 0 | } |
97 | | |
98 | | // https://www.w3.org/TR/css-backgrounds-3/#backgrounds |
99 | | void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, CSSPixelRect const& border_rect, Color background_color, CSS::ImageRendering image_rendering, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiiData const& border_radii) |
100 | 0 | { |
101 | 0 | RefPtr<DisplayList> text_clip; |
102 | 0 | if (background_layers && !background_layers->is_empty() && background_layers->last().clip == CSS::BackgroundBox::Text) { |
103 | 0 | text_clip = compute_text_clip_paths(context, *layout_node.paintable()); |
104 | 0 | } |
105 | |
|
106 | 0 | auto& display_list_recorder = context.display_list_recorder(); |
107 | |
|
108 | 0 | struct BackgroundBox { |
109 | 0 | CSSPixelRect rect; |
110 | 0 | BorderRadiiData radii; |
111 | |
|
112 | 0 | inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) |
113 | 0 | { |
114 | 0 | rect.shrink(top, right, bottom, left); |
115 | 0 | radii.shrink(top, right, bottom, left); |
116 | 0 | } |
117 | 0 | }; |
118 | |
|
119 | 0 | BackgroundBox border_box { |
120 | 0 | border_rect, |
121 | 0 | border_radii |
122 | 0 | }; |
123 | |
|
124 | 0 | auto get_box = [&](CSS::BackgroundBox box_clip) { |
125 | 0 | auto box = border_box; |
126 | 0 | switch (box_clip) { |
127 | 0 | case CSS::BackgroundBox::ContentBox: { |
128 | 0 | auto& padding = layout_node.box_model().padding; |
129 | 0 | box.shrink(padding.top, padding.right, padding.bottom, padding.left); |
130 | 0 | [[fallthrough]]; |
131 | 0 | } |
132 | 0 | case CSS::BackgroundBox::PaddingBox: { |
133 | 0 | auto& border = layout_node.box_model().border; |
134 | 0 | box.shrink(border.top, border.right, border.bottom, border.left); |
135 | 0 | [[fallthrough]]; |
136 | 0 | } |
137 | 0 | case CSS::BackgroundBox::BorderBox: |
138 | 0 | default: |
139 | 0 | return box; |
140 | 0 | } |
141 | 0 | }; |
142 | |
|
143 | 0 | auto color_box = border_box; |
144 | 0 | if (background_layers && !background_layers->is_empty()) |
145 | 0 | color_box = get_box(background_layers->last().clip); |
146 | |
|
147 | 0 | auto layer_is_paintable = [&](auto& layer) { |
148 | 0 | return layer.background_image && layer.background_image->is_paintable(); |
149 | 0 | }; |
150 | |
|
151 | 0 | bool has_paintable_layers = false; |
152 | 0 | if (background_layers) { |
153 | 0 | for (auto& layer : *background_layers) { |
154 | 0 | if (layer_is_paintable(layer)) { |
155 | 0 | has_paintable_layers = true; |
156 | 0 | break; |
157 | 0 | } |
158 | 0 | } |
159 | 0 | } |
160 | |
|
161 | 0 | display_list_recorder.fill_rect_with_rounded_corners( |
162 | 0 | context.rounded_device_rect(color_box.rect).to_type<int>(), |
163 | 0 | background_color, |
164 | 0 | color_box.radii.top_left.as_corner(context), |
165 | 0 | color_box.radii.top_right.as_corner(context), |
166 | 0 | color_box.radii.bottom_right.as_corner(context), |
167 | 0 | color_box.radii.bottom_left.as_corner(context), |
168 | 0 | text_clip); |
169 | |
|
170 | 0 | if (!has_paintable_layers) |
171 | 0 | return; |
172 | | |
173 | 0 | struct { |
174 | 0 | DevicePixels top { 0 }; |
175 | 0 | DevicePixels bottom { 0 }; |
176 | 0 | DevicePixels left { 0 }; |
177 | 0 | DevicePixels right { 0 }; |
178 | 0 | } clip_shrink; |
179 | |
|
180 | 0 | auto border_top = layout_node.computed_values().border_top(); |
181 | 0 | auto border_bottom = layout_node.computed_values().border_bottom(); |
182 | 0 | auto border_left = layout_node.computed_values().border_left(); |
183 | 0 | auto border_right = layout_node.computed_values().border_right(); |
184 | |
|
185 | 0 | if (border_top.color.alpha() == 255 && border_bottom.color.alpha() == 255 |
186 | 0 | && border_left.color.alpha() == 255 && border_right.color.alpha() == 255) { |
187 | 0 | clip_shrink.top = context.rounded_device_pixels(border_top.width); |
188 | 0 | clip_shrink.bottom = context.rounded_device_pixels(border_bottom.width); |
189 | 0 | clip_shrink.left = context.rounded_device_pixels(border_left.width); |
190 | 0 | clip_shrink.right = context.rounded_device_pixels(border_right.width); |
191 | 0 | } |
192 | | |
193 | | // Note: Background layers are ordered front-to-back, so we paint them in reverse |
194 | 0 | for (auto& layer : background_layers->in_reverse()) { |
195 | 0 | if (!layer_is_paintable(layer)) |
196 | 0 | continue; |
197 | 0 | DisplayListRecorderStateSaver state { display_list_recorder }; |
198 | | |
199 | | // Clip |
200 | 0 | auto clip_box = get_box(layer.clip); |
201 | |
|
202 | 0 | CSSPixelRect const& css_clip_rect = clip_box.rect; |
203 | 0 | auto clip_rect = context.rounded_device_rect(css_clip_rect); |
204 | 0 | display_list_recorder.add_clip_rect(clip_rect.to_type<int>()); |
205 | 0 | ScopedCornerRadiusClip corner_clip { context, clip_rect, clip_box.radii }; |
206 | |
|
207 | 0 | if (layer.clip == CSS::BackgroundBox::BorderBox) { |
208 | | // Shrink the effective clip rect if to account for the bits the borders will definitely paint over |
209 | | // (if they all have alpha == 255). |
210 | 0 | clip_rect.shrink(clip_shrink.top, clip_shrink.right, clip_shrink.bottom, clip_shrink.left); |
211 | 0 | } |
212 | |
|
213 | 0 | auto& image = *layer.background_image; |
214 | 0 | CSSPixelRect background_positioning_area; |
215 | | |
216 | | // Attachment and Origin |
217 | 0 | switch (layer.attachment) { |
218 | 0 | case CSS::BackgroundAttachment::Fixed: |
219 | 0 | background_positioning_area = layout_node.root().navigable()->viewport_rect(); |
220 | 0 | break; |
221 | 0 | case CSS::BackgroundAttachment::Local: |
222 | 0 | background_positioning_area = get_box(layer.origin).rect; |
223 | 0 | if (is<Layout::Box>(layout_node)) { |
224 | 0 | auto* paintable_box = static_cast<Layout::Box const&>(layout_node).paintable_box(); |
225 | 0 | if (paintable_box && !paintable_box->is_viewport()) { |
226 | 0 | auto scroll_offset = paintable_box->scroll_offset(); |
227 | 0 | background_positioning_area.translate_by(-scroll_offset.x(), -scroll_offset.y()); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | break; |
231 | 0 | case CSS::BackgroundAttachment::Scroll: |
232 | 0 | background_positioning_area = get_box(layer.origin).rect; |
233 | 0 | break; |
234 | 0 | } |
235 | | |
236 | 0 | Optional<CSSPixels> specified_width {}; |
237 | 0 | Optional<CSSPixels> specified_height {}; |
238 | 0 | if (layer.size_type == CSS::BackgroundSize::LengthPercentage) { |
239 | 0 | if (!layer.size_x.is_auto()) |
240 | 0 | specified_width = layer.size_x.to_px(layout_node, background_positioning_area.width()); |
241 | 0 | if (!layer.size_y.is_auto()) |
242 | 0 | specified_height = layer.size_y.to_px(layout_node, background_positioning_area.height()); |
243 | 0 | } |
244 | 0 | auto concrete_image_size = run_default_sizing_algorithm( |
245 | 0 | specified_width, specified_height, |
246 | 0 | image.natural_width(), image.natural_height(), image.natural_aspect_ratio(), |
247 | 0 | background_positioning_area.size()); |
248 | | |
249 | | // If any of these are zero, the NaNs will pop up in the painting code. |
250 | 0 | if (background_positioning_area.is_empty() || concrete_image_size.is_empty()) |
251 | 0 | continue; |
252 | | |
253 | | // Size |
254 | 0 | CSSPixelRect image_rect; |
255 | 0 | switch (layer.size_type) { |
256 | 0 | case CSS::BackgroundSize::Contain: { |
257 | 0 | double max_width_ratio = background_positioning_area.width().to_double() / concrete_image_size.width().to_double(); |
258 | 0 | double max_height_ratio = background_positioning_area.height().to_double() / concrete_image_size.height().to_double(); |
259 | 0 | double ratio = min(max_width_ratio, max_height_ratio); |
260 | 0 | image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio)); |
261 | 0 | break; |
262 | 0 | } |
263 | 0 | case CSS::BackgroundSize::Cover: { |
264 | 0 | double max_width_ratio = background_positioning_area.width().to_double() / concrete_image_size.width().to_double(); |
265 | 0 | double max_height_ratio = background_positioning_area.height().to_double() / concrete_image_size.height().to_double(); |
266 | 0 | double ratio = max(max_width_ratio, max_height_ratio); |
267 | 0 | image_rect.set_size(concrete_image_size.width().scaled(ratio), concrete_image_size.height().scaled(ratio)); |
268 | 0 | break; |
269 | 0 | } |
270 | 0 | case CSS::BackgroundSize::LengthPercentage: |
271 | 0 | image_rect.set_size(concrete_image_size); |
272 | 0 | break; |
273 | 0 | } |
274 | | |
275 | | // If after sizing we have a 0px image, we're done. Attempting to paint this would be an infinite loop. |
276 | 0 | if (image_rect.is_empty()) |
277 | 0 | continue; |
278 | | |
279 | | // If background-repeat is round for one (or both) dimensions, there is a second step. |
280 | | // The UA must scale the image in that dimension (or both dimensions) so that it fits a |
281 | | // whole number of times in the background positioning area. |
282 | 0 | if (layer.repeat_x == CSS::Repeat::Round || layer.repeat_y == CSS::Repeat::Round) { |
283 | | // If X ≠ 0 is the width of the image after step one and W is the width of the |
284 | | // background positioning area, then the rounded width X' = W / round(W / X) |
285 | | // where round() is a function that returns the nearest natural number |
286 | | // (integer greater than zero). |
287 | 0 | if (layer.repeat_x == CSS::Repeat::Round) { |
288 | 0 | image_rect.set_width(background_positioning_area.width() / round(background_positioning_area.width() / image_rect.width())); |
289 | 0 | } |
290 | 0 | if (layer.repeat_y == CSS::Repeat::Round) { |
291 | 0 | image_rect.set_height(background_positioning_area.height() / round(background_positioning_area.height() / image_rect.height())); |
292 | 0 | } |
293 | | |
294 | | // If background-repeat is round for one dimension only and if background-size is auto |
295 | | // for the other dimension, then there is a third step: that other dimension is scaled |
296 | | // so that the original aspect ratio is restored. |
297 | 0 | if (layer.repeat_x != layer.repeat_y) { |
298 | 0 | if (layer.size_x.is_auto()) { |
299 | 0 | image_rect.set_width(image_rect.height() * (concrete_image_size.width() / concrete_image_size.height())); |
300 | 0 | } |
301 | 0 | if (layer.size_y.is_auto()) { |
302 | 0 | image_rect.set_height(image_rect.width() * (concrete_image_size.height() / concrete_image_size.width())); |
303 | 0 | } |
304 | 0 | } |
305 | 0 | } |
306 | |
|
307 | 0 | CSSPixels space_x = background_positioning_area.width() - image_rect.width(); |
308 | 0 | CSSPixels space_y = background_positioning_area.height() - image_rect.height(); |
309 | | |
310 | | // Position |
311 | 0 | CSSPixels offset_x = layer.position_offset_x.to_px(layout_node, space_x); |
312 | 0 | if (layer.position_edge_x == CSS::PositionEdge::Right) { |
313 | 0 | image_rect.set_right_without_resize(background_positioning_area.right() - offset_x); |
314 | 0 | } else { |
315 | 0 | image_rect.set_left(background_positioning_area.left() + offset_x); |
316 | 0 | } |
317 | |
|
318 | 0 | CSSPixels offset_y = layer.position_offset_y.to_px(layout_node, space_y); |
319 | 0 | if (layer.position_edge_y == CSS::PositionEdge::Bottom) { |
320 | 0 | image_rect.set_bottom_without_resize(background_positioning_area.bottom() - offset_y); |
321 | 0 | } else { |
322 | 0 | image_rect.set_top(background_positioning_area.top() + offset_y); |
323 | 0 | } |
324 | | |
325 | | // Repetition |
326 | 0 | bool repeat_x = false; |
327 | 0 | bool repeat_y = false; |
328 | 0 | CSSPixels x_step = 0; |
329 | 0 | CSSPixels y_step = 0; |
330 | |
|
331 | 0 | switch (layer.repeat_x) { |
332 | 0 | case CSS::Repeat::Round: |
333 | 0 | x_step = image_rect.width(); |
334 | 0 | repeat_x = true; |
335 | 0 | break; |
336 | 0 | case CSS::Repeat::Space: { |
337 | 0 | int whole_images = (background_positioning_area.width() / image_rect.width()).to_int(); |
338 | 0 | if (whole_images <= 1) { |
339 | 0 | x_step = image_rect.width(); |
340 | 0 | repeat_x = false; |
341 | 0 | } else { |
342 | 0 | auto space = fmod(background_positioning_area.width().to_double(), image_rect.width().to_double()); |
343 | 0 | x_step = image_rect.width() + CSSPixels::nearest_value_for(space / static_cast<double>(whole_images - 1)); |
344 | 0 | repeat_x = true; |
345 | 0 | } |
346 | 0 | break; |
347 | 0 | } |
348 | 0 | case CSS::Repeat::Repeat: |
349 | 0 | x_step = image_rect.width(); |
350 | 0 | repeat_x = true; |
351 | 0 | break; |
352 | 0 | case CSS::Repeat::NoRepeat: |
353 | 0 | repeat_x = false; |
354 | 0 | break; |
355 | 0 | } |
356 | | // Move image_rect to the left-most tile position that is still visible |
357 | 0 | if (repeat_x && image_rect.x() > css_clip_rect.x()) { |
358 | 0 | auto x_delta = floor(x_step * ceil((image_rect.x() - css_clip_rect.x()) / x_step)); |
359 | 0 | image_rect.set_x(image_rect.x() - x_delta); |
360 | 0 | } |
361 | |
|
362 | 0 | switch (layer.repeat_y) { |
363 | 0 | case CSS::Repeat::Round: |
364 | 0 | y_step = image_rect.height(); |
365 | 0 | repeat_y = true; |
366 | 0 | break; |
367 | 0 | case CSS::Repeat::Space: { |
368 | 0 | int whole_images = (background_positioning_area.height() / image_rect.height()).to_int(); |
369 | 0 | if (whole_images <= 1) { |
370 | 0 | y_step = image_rect.height(); |
371 | 0 | repeat_y = false; |
372 | 0 | } else { |
373 | 0 | auto space = fmod(background_positioning_area.height().to_float(), image_rect.height().to_float()); |
374 | 0 | y_step = image_rect.height() + CSSPixels::nearest_value_for(static_cast<double>(space) / static_cast<double>(whole_images - 1)); |
375 | 0 | repeat_y = true; |
376 | 0 | } |
377 | 0 | break; |
378 | 0 | } |
379 | 0 | case CSS::Repeat::Repeat: |
380 | 0 | y_step = image_rect.height(); |
381 | 0 | repeat_y = true; |
382 | 0 | break; |
383 | 0 | case CSS::Repeat::NoRepeat: |
384 | 0 | repeat_y = false; |
385 | 0 | break; |
386 | 0 | } |
387 | | // Move image_rect to the top-most tile position that is still visible |
388 | 0 | if (repeat_y && image_rect.y() > css_clip_rect.y()) { |
389 | 0 | auto y_delta = floor(y_step * ceil((image_rect.y() - css_clip_rect.y()) / y_step)); |
390 | 0 | image_rect.set_y(image_rect.y() - y_delta); |
391 | 0 | } |
392 | |
|
393 | 0 | CSSPixels initial_image_x = image_rect.x(); |
394 | 0 | CSSPixels image_y = image_rect.y(); |
395 | |
|
396 | 0 | image.resolve_for_size(layout_node, image_rect.size()); |
397 | |
|
398 | 0 | auto for_each_image_device_rect = [&](auto callback) { |
399 | 0 | while (image_y < css_clip_rect.bottom()) { |
400 | 0 | image_rect.set_y(image_y); |
401 | |
|
402 | 0 | auto image_x = initial_image_x; |
403 | 0 | while (image_x < css_clip_rect.right()) { |
404 | 0 | image_rect.set_x(image_x); |
405 | 0 | auto image_device_rect = context.rounded_device_rect(image_rect); |
406 | 0 | callback(image_device_rect); |
407 | 0 | if (!repeat_x) |
408 | 0 | break; |
409 | 0 | image_x += x_step; |
410 | 0 | } |
411 | |
|
412 | 0 | if (!repeat_y) |
413 | 0 | break; |
414 | 0 | image_y += y_step; |
415 | 0 | } |
416 | 0 | }; Unexecuted instantiation: BackgroundPainting.cpp:auto Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_2::operator()<Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_1>(Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_1) const Unexecuted instantiation: BackgroundPainting.cpp:auto Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_2::operator()<Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_3>(Web::Painting::paint_background(Web::PaintContext&, Web::Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::Rect<Web::CSSPixels> const&, Gfx::Color, Web::CSS::ImageRendering, AK::Vector<Web::CSS::BackgroundLayerData, 0ul> const*, Web::Painting::BorderRadiiData const&)::$_3) const |
417 | |
|
418 | 0 | if (auto color = image.color_if_single_pixel_bitmap(); color.has_value()) { |
419 | | // OPTIMIZATION: If the image is a single pixel, we can just fill the whole area with it. |
420 | | // However, we must first figure out the real coverage area, taking repeat etc into account. |
421 | | |
422 | | // FIXME: This could be written in a far more efficient way. |
423 | 0 | auto fill_rect = Optional<DevicePixelRect> {}; |
424 | 0 | for_each_image_device_rect([&](auto const& image_device_rect) { |
425 | 0 | if (!fill_rect.has_value()) { |
426 | 0 | fill_rect = image_device_rect; |
427 | 0 | } else { |
428 | 0 | fill_rect = fill_rect->united(image_device_rect); |
429 | 0 | } |
430 | 0 | }); |
431 | 0 | display_list_recorder.fill_rect(fill_rect->to_type<int>(), color.value(), text_clip); |
432 | 0 | } else { |
433 | 0 | for_each_image_device_rect([&](auto const& image_device_rect) { |
434 | 0 | image.paint(context, image_device_rect, image_rendering, text_clip); |
435 | 0 | }); |
436 | 0 | } |
437 | 0 | } |
438 | 0 | } |
439 | | |
440 | | } |