1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 12:17:44 +00:00

LibWeb/Painting: Do not paint box outer shadows not visible in viewport

This change separates the box outer shadow metrics calculations into a
separate function. This function is then used to obtain the shadow
bounding rectangle and skip painting if the entire shadow is outside
of the viewport.
This commit is contained in:
Aliaksandr Kalenik 2023-10-19 01:16:01 +02:00 committed by Andreas Kling
parent 596bc76b7a
commit 99217bf6db
3 changed files with 192 additions and 40 deletions

View file

@ -328,6 +328,9 @@ CommandResult PaintConicGradient::execute(CommandExecutionState& state) const
CommandResult PaintOuterBoxShadow::execute(CommandExecutionState& state) const CommandResult PaintOuterBoxShadow::execute(CommandExecutionState& state) const
{ {
auto bounding_rect = get_outer_box_shadow_bounding_rect(outer_box_shadow_params);
if (state.would_be_fully_clipped_by_painter(bounding_rect))
return CommandResult::Continue;
auto& painter = state.painter(); auto& painter = state.painter();
paint_outer_box_shadow(painter, outer_box_shadow_params); paint_outer_box_shadow(painter, outer_box_shadow_params);
return CommandResult::Continue; return CommandResult::Continue;

View file

@ -72,11 +72,47 @@ void paint_inner_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams par
*shadow_bitmap, shadow_bitmap->rect(), params.box_shadow_data.color.alpha() / 255.); *shadow_bitmap, shadow_bitmap->rect(), params.box_shadow_data.color.alpha() / 255.);
} }
void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams params) struct OuterBoxShadowMetrics {
{ DevicePixelRect shadow_bitmap_rect;
auto const& border_radii = params.border_radii; DevicePixelRect non_blurred_shadow_rect;
auto const& box_shadow_data = params.box_shadow_data; DevicePixelRect inner_bounding_rect;
DevicePixels blurred_edge_thickness;
DevicePixels double_radius;
DevicePixels blur_radius;
DevicePixelRect top_left_corner_rect;
DevicePixelRect top_right_corner_rect;
DevicePixelRect bottom_right_corner_rect;
DevicePixelRect bottom_left_corner_rect;
DevicePixelPoint top_left_corner_blit_pos;
DevicePixelPoint top_right_corner_blit_pos;
DevicePixelPoint bottom_right_corner_blit_pos;
DevicePixelPoint bottom_left_corner_blit_pos;
DevicePixelSize top_left_corner_size;
DevicePixelSize top_right_corner_size;
DevicePixelSize bottom_right_corner_size;
DevicePixelSize bottom_left_corner_size;
DevicePixels left_start;
DevicePixels top_start;
DevicePixels right_start;
DevicePixels bottom_start;
DevicePixelRect left_edge_rect;
DevicePixelRect right_edge_rect;
DevicePixelRect top_edge_rect;
DevicePixelRect bottom_edge_rect;
CornerRadius top_left_shadow_corner;
CornerRadius top_right_shadow_corner;
CornerRadius bottom_right_shadow_corner;
CornerRadius bottom_left_shadow_corner;
};
static OuterBoxShadowMetrics get_outer_box_shadow_configuration(PaintOuterBoxShadowParams params)
{
auto device_content_rect = params.device_content_rect; auto device_content_rect = params.device_content_rect;
auto top_left_corner = params.corner_radii.top_left; auto top_left_corner = params.corner_radii.top_left;
@ -89,24 +125,9 @@ void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams par
DevicePixels blur_radius = params.blur_radius; DevicePixels blur_radius = params.blur_radius;
DevicePixels spread_distance = params.spread_distance; DevicePixels spread_distance = params.spread_distance;
auto fill_rect_masked = [](auto& painter, auto fill_rect, auto mask_rect, auto color) {
Gfx::DisjointRectSet<DevicePixels> rect_set;
rect_set.add(fill_rect);
auto shattered = rect_set.shatter(mask_rect);
for (auto& rect : shattered.rects())
painter.fill_rect(rect.template to_type<int>(), color);
};
// Our blur cannot handle radii over 255 so there's no point trying (255 is silly big anyway) // Our blur cannot handle radii over 255 so there's no point trying (255 is silly big anyway)
blur_radius = clamp(blur_radius, 0, 255); blur_radius = clamp(blur_radius, 0, 255);
// If there's no blurring, nor rounded corners, we can save a lot of effort.
auto non_blurred_shadow_rect = device_content_rect.inflated(spread_distance, spread_distance, spread_distance, spread_distance);
if (blur_radius == 0 && !border_radii.has_any_radius()) {
fill_rect_masked(painter, non_blurred_shadow_rect.translated(offset_x, offset_y), device_content_rect, box_shadow_data.color);
return;
}
auto top_left_shadow_corner = top_left_corner; auto top_left_shadow_corner = top_left_corner;
auto top_right_shadow_corner = top_right_corner; auto top_right_shadow_corner = top_right_corner;
auto bottom_right_shadow_corner = bottom_right_corner; auto bottom_right_shadow_corner = bottom_right_corner;
@ -145,6 +166,8 @@ void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams par
auto bottom_left_corner_size = (bottom_left_shadow_corner ? bottom_left_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>(); auto bottom_left_corner_size = (bottom_left_shadow_corner ? bottom_left_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
auto bottom_right_corner_size = (bottom_right_shadow_corner ? bottom_right_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>(); auto bottom_right_corner_size = (bottom_right_shadow_corner ? bottom_right_shadow_corner.as_rect().size() : default_corner_size).to_type<DevicePixels>();
auto non_blurred_shadow_rect = device_content_rect.inflated(spread_distance, spread_distance, spread_distance, spread_distance);
auto max_edge_width = non_blurred_shadow_rect.width() / 2; auto max_edge_width = non_blurred_shadow_rect.width() / 2;
auto max_edge_height = non_blurred_shadow_rect.height() / 2; auto max_edge_height = non_blurred_shadow_rect.height() / 2;
auto extra_edge_width = non_blurred_shadow_rect.width() % 2; auto extra_edge_width = non_blurred_shadow_rect.width() % 2;
@ -213,20 +236,139 @@ void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams par
DevicePixelRect top_edge_rect { top_left_corner_rect.width(), 0, 1, horizontal_top_edge_width }; DevicePixelRect top_edge_rect { top_left_corner_rect.width(), 0, 1, horizontal_top_edge_width };
DevicePixelRect bottom_edge_rect { bottom_left_corner_rect.width(), shadow_bitmap_rect.height() - horizontal_edge_width, 1, horizontal_edge_width }; DevicePixelRect bottom_edge_rect { bottom_left_corner_rect.width(), shadow_bitmap_rect.height() - horizontal_edge_width, 1, horizontal_edge_width };
auto shadows_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, shadow_bitmap_rect.size().to_type<int>()); auto left_start = inner_bounding_rect.left() - blurred_edge_thickness;
if (shadows_bitmap.is_error()) { auto right_start = inner_bounding_rect.left() + inner_bounding_rect.width() + (blurred_edge_thickness - vertical_edge_width);
dbgln("Unable to allocate temporary bitmap {} for box-shadow rendering: {}", shadow_bitmap_rect, shadows_bitmap.error()); auto top_start = inner_bounding_rect.top() - blurred_edge_thickness;
auto bottom_start = inner_bounding_rect.top() + inner_bounding_rect.height() + (blurred_edge_thickness - horizontal_edge_width);
auto top_left_corner_blit_pos = inner_bounding_rect.top_left().translated(-blurred_edge_thickness, -blurred_edge_thickness);
auto top_right_corner_blit_pos = inner_bounding_rect.top_right().translated(-top_right_corner_size.width() + double_radius, -blurred_edge_thickness);
auto bottom_left_corner_blit_pos = inner_bounding_rect.bottom_left().translated(-blurred_edge_thickness, -bottom_left_corner_size.height() + double_radius);
auto bottom_right_corner_blit_pos = inner_bounding_rect.bottom_right().translated(-bottom_right_corner_size.width() + double_radius, -bottom_right_corner_size.height() + double_radius);
return OuterBoxShadowMetrics {
.shadow_bitmap_rect = shadow_bitmap_rect,
.non_blurred_shadow_rect = non_blurred_shadow_rect,
.inner_bounding_rect = inner_bounding_rect,
.blurred_edge_thickness = blurred_edge_thickness,
.double_radius = double_radius,
.blur_radius = blur_radius,
.top_left_corner_rect = top_left_corner_rect,
.top_right_corner_rect = top_right_corner_rect,
.bottom_right_corner_rect = bottom_right_corner_rect,
.bottom_left_corner_rect = bottom_left_corner_rect,
.top_left_corner_blit_pos = top_left_corner_blit_pos,
.top_right_corner_blit_pos = top_right_corner_blit_pos,
.bottom_right_corner_blit_pos = bottom_right_corner_blit_pos,
.bottom_left_corner_blit_pos = bottom_left_corner_blit_pos,
.top_left_corner_size = top_left_corner_size,
.top_right_corner_size = top_right_corner_size,
.bottom_right_corner_size = bottom_right_corner_size,
.bottom_left_corner_size = bottom_left_corner_size,
.left_start = left_start,
.top_start = top_start,
.right_start = right_start,
.bottom_start = bottom_start,
.left_edge_rect = left_edge_rect,
.right_edge_rect = right_edge_rect,
.top_edge_rect = top_edge_rect,
.bottom_edge_rect = bottom_edge_rect,
.top_left_shadow_corner = top_left_shadow_corner,
.top_right_shadow_corner = top_right_shadow_corner,
.bottom_right_shadow_corner = bottom_right_shadow_corner,
.bottom_left_shadow_corner = bottom_left_shadow_corner,
};
}
Gfx::IntRect get_outer_box_shadow_bounding_rect(PaintOuterBoxShadowParams params)
{
auto shadow_config = get_outer_box_shadow_configuration(params);
auto const& top_left_corner_blit_pos = shadow_config.top_left_corner_blit_pos;
auto const& top_right_corner_blit_pos = shadow_config.top_right_corner_blit_pos;
auto const& bottom_left_corner_blit_pos = shadow_config.bottom_left_corner_blit_pos;
auto const& top_right_corner_rect = shadow_config.top_right_corner_rect;
auto const& bottom_left_corner_rect = shadow_config.bottom_left_corner_rect;
return Gfx::IntRect {
top_left_corner_blit_pos,
{ top_right_corner_blit_pos.x() - top_left_corner_blit_pos.x() + top_right_corner_rect.width(),
bottom_left_corner_blit_pos.y() - top_left_corner_blit_pos.y() + bottom_left_corner_rect.height() }
};
}
void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams params)
{
auto const& box_shadow_data = params.box_shadow_data;
auto const& device_content_rect = params.device_content_rect;
auto const& border_radii = params.border_radii;
auto const& top_left_corner = params.corner_radii.top_left;
auto const& top_right_corner = params.corner_radii.top_right;
auto const& bottom_right_corner = params.corner_radii.bottom_right;
auto const& bottom_left_corner = params.corner_radii.bottom_left;
DevicePixels offset_x = params.offset_x;
DevicePixels offset_y = params.offset_y;
auto shadow_config = get_outer_box_shadow_configuration(params);
auto const& shadow_bitmap_rect = shadow_config.shadow_bitmap_rect;
auto const& non_blurred_shadow_rect = shadow_config.non_blurred_shadow_rect;
auto const& inner_bounding_rect = shadow_config.inner_bounding_rect;
auto const& blurred_edge_thickness = shadow_config.blurred_edge_thickness;
auto const& double_radius = shadow_config.double_radius;
auto const& blur_radius = shadow_config.blur_radius;
auto const& top_left_corner_rect = shadow_config.top_left_corner_rect;
auto const& top_right_corner_rect = shadow_config.top_right_corner_rect;
auto const& bottom_right_corner_rect = shadow_config.bottom_right_corner_rect;
auto const& bottom_left_corner_rect = shadow_config.bottom_left_corner_rect;
auto const& top_left_corner_blit_pos = shadow_config.top_left_corner_blit_pos;
auto const& top_right_corner_blit_pos = shadow_config.top_right_corner_blit_pos;
auto const& bottom_right_corner_blit_pos = shadow_config.bottom_right_corner_blit_pos;
auto const& bottom_left_corner_blit_pos = shadow_config.bottom_left_corner_blit_pos;
auto const& top_left_corner_size = shadow_config.top_left_corner_size;
auto const& top_right_corner_size = shadow_config.top_right_corner_size;
auto const& bottom_right_corner_size = shadow_config.bottom_right_corner_size;
auto const& bottom_left_corner_size = shadow_config.bottom_left_corner_size;
auto const& left_start = shadow_config.left_start;
auto const& top_start = shadow_config.top_start;
auto const& right_start = shadow_config.right_start;
auto const& bottom_start = shadow_config.bottom_start;
auto const& left_edge_rect = shadow_config.left_edge_rect;
auto const& right_edge_rect = shadow_config.right_edge_rect;
auto const& top_edge_rect = shadow_config.top_edge_rect;
auto const& bottom_edge_rect = shadow_config.bottom_edge_rect;
auto const& top_left_shadow_corner = shadow_config.top_left_shadow_corner;
auto const& top_right_shadow_corner = shadow_config.top_right_shadow_corner;
auto const& bottom_right_shadow_corner = shadow_config.bottom_right_shadow_corner;
auto const& bottom_left_shadow_corner = shadow_config.bottom_left_shadow_corner;
auto fill_rect_masked = [](auto& painter, auto fill_rect, auto mask_rect, auto color) {
Gfx::DisjointRectSet<DevicePixels> rect_set;
rect_set.add(fill_rect);
auto shattered = rect_set.shatter(mask_rect);
for (auto& rect : shattered.rects())
painter.fill_rect(rect.template to_type<int>(), color);
};
// If there's no blurring, nor rounded corners, we can save a lot of effort.
if (blur_radius == 0 && !border_radii.has_any_radius()) {
fill_rect_masked(painter, non_blurred_shadow_rect.translated(offset_x, offset_y), device_content_rect, box_shadow_data.color);
return; return;
} }
auto shadow_bitmap = shadows_bitmap.release_value();
Gfx::Painter corner_painter { *shadow_bitmap };
Gfx::AntiAliasingPainter aa_corner_painter { corner_painter };
aa_corner_painter.fill_rect_with_rounded_corners(
shadow_bitmap_rect.shrunken(double_radius, double_radius, double_radius, double_radius).to_type<int>(),
box_shadow_data.color, top_left_shadow_corner, top_right_shadow_corner, bottom_right_shadow_corner, bottom_left_shadow_corner);
Gfx::StackBlurFilter filter(*shadow_bitmap);
filter.process_rgba(blur_radius.value(), box_shadow_data.color);
auto paint_shadow_infill = [&] { auto paint_shadow_infill = [&] {
if (!params.border_radii.has_any_radius()) if (!params.border_radii.has_any_radius())
@ -279,15 +421,20 @@ void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams par
painter.fill_rect(inner.to_type<int>(), box_shadow_data.color); painter.fill_rect(inner.to_type<int>(), box_shadow_data.color);
}; };
auto left_start = inner_bounding_rect.left() - blurred_edge_thickness; auto shadows_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, shadow_bitmap_rect.size().to_type<int>());
auto right_start = inner_bounding_rect.left() + inner_bounding_rect.width() + (blurred_edge_thickness - vertical_edge_width); if (shadows_bitmap.is_error()) {
auto top_start = inner_bounding_rect.top() - blurred_edge_thickness; dbgln("Unable to allocate temporary bitmap {} for box-shadow rendering: {}", shadow_bitmap_rect, shadows_bitmap.error());
auto bottom_start = inner_bounding_rect.top() + inner_bounding_rect.height() + (blurred_edge_thickness - horizontal_edge_width); return;
}
auto shadow_bitmap = shadows_bitmap.release_value();
Gfx::Painter corner_painter { *shadow_bitmap };
Gfx::AntiAliasingPainter aa_corner_painter { corner_painter };
auto top_left_corner_blit_pos = inner_bounding_rect.top_left().translated(-blurred_edge_thickness, -blurred_edge_thickness); aa_corner_painter.fill_rect_with_rounded_corners(
auto top_right_corner_blit_pos = inner_bounding_rect.top_right().translated(-top_right_corner_size.width() + double_radius, -blurred_edge_thickness); shadow_bitmap_rect.shrunken(double_radius, double_radius, double_radius, double_radius).to_type<int>(),
auto bottom_left_corner_blit_pos = inner_bounding_rect.bottom_left().translated(-blurred_edge_thickness, -bottom_left_corner_size.height() + double_radius); box_shadow_data.color, top_left_shadow_corner, top_right_shadow_corner, bottom_right_shadow_corner, bottom_left_shadow_corner);
auto bottom_right_corner_blit_pos = inner_bounding_rect.bottom_right().translated(-bottom_right_corner_size.width() + double_radius, -bottom_right_corner_size.height() + double_radius); Gfx::StackBlurFilter filter(*shadow_bitmap);
filter.process_rgba(blur_radius.value(), box_shadow_data.color);
auto paint_shadow = [&](DevicePixelRect clip_rect) { auto paint_shadow = [&](DevicePixelRect clip_rect) {
Gfx::PainterStateSaver save { painter }; Gfx::PainterStateSaver save { painter };

View file

@ -14,9 +14,11 @@
namespace Web::Painting { namespace Web::Painting {
void paint_outer_box_shadow(Gfx::Painter&, PaintOuterBoxShadowParams params);
void paint_inner_box_shadow(Gfx::Painter&, PaintOuterBoxShadowParams params); void paint_inner_box_shadow(Gfx::Painter&, PaintOuterBoxShadowParams params);
Gfx::IntRect get_outer_box_shadow_bounding_rect(PaintOuterBoxShadowParams params);
void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams params);
void paint_box_shadow( void paint_box_shadow(
PaintContext&, PaintContext&,
CSSPixelRect const& bordered_content_rect, CSSPixelRect const& bordered_content_rect,