LibWeb: Use rounding instead of enclosing_int_rect() when painting

By using enclosing_int_rect(), borders and backgrounds of boxes were
sometimes 1 pixel off, making things slightly larger than they should
be. Fix this by using to_rounded() instead of enclosing_int_rect().

There's definitely more of these type of issues lurking in the code,
and we'll get to them in time.
This commit is contained in:
Andreas Kling 2022-03-29 15:43:28 +02:00
parent e1cf51b0bd
commit 0de488749f
Notes: sideshowbarker 2024-07-17 16:34:26 +09:00
5 changed files with 18 additions and 20 deletions

View File

@ -14,7 +14,7 @@
namespace Web::Painting { namespace Web::Painting {
// https://www.w3.org/TR/css-backgrounds-3/#backgrounds // https://www.w3.org/TR/css-backgrounds-3/#backgrounds
void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, Gfx::IntRect const& border_rect, Color background_color, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiusData const& border_radius) void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, Gfx::FloatRect const& border_rect, Color background_color, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiusData const& border_radius)
{ {
auto& painter = context.painter(); auto& painter = context.painter();
@ -41,7 +41,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
if (background_layers && !background_layers->is_empty()) if (background_layers && !background_layers->is_empty())
color_rect = get_box(background_layers->last().clip); color_rect = get_box(background_layers->last().clip);
// FIXME: Support elliptical corners // FIXME: Support elliptical corners
painter.fill_rect_with_rounded_corners(color_rect, background_color, border_radius.top_left, border_radius.top_right, border_radius.bottom_right, border_radius.bottom_left); painter.fill_rect_with_rounded_corners(color_rect.to_rounded<int>(), background_color, border_radius.top_left, border_radius.top_right, border_radius.bottom_right, border_radius.bottom_left);
if (!background_layers) if (!background_layers)
return; return;
@ -57,14 +57,14 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
// Clip // Clip
auto clip_rect = get_box(layer.clip); auto clip_rect = get_box(layer.clip);
painter.save(); painter.save();
painter.add_clip_rect(clip_rect); painter.add_clip_rect(clip_rect.to_rounded<int>());
Gfx::IntRect background_positioning_area; Gfx::FloatRect background_positioning_area;
// Attachment and Origin // Attachment and Origin
switch (layer.attachment) { switch (layer.attachment) {
case CSS::BackgroundAttachment::Fixed: case CSS::BackgroundAttachment::Fixed:
background_positioning_area = layout_node.root().browsing_context().viewport_rect(); background_positioning_area = layout_node.root().browsing_context().viewport_rect().to_type<float>();
break; break;
case CSS::BackgroundAttachment::Local: case CSS::BackgroundAttachment::Local:
case CSS::BackgroundAttachment::Scroll: case CSS::BackgroundAttachment::Scroll:
@ -76,15 +76,15 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
Gfx::IntRect image_rect; Gfx::IntRect image_rect;
switch (layer.size_type) { switch (layer.size_type) {
case CSS::BackgroundSize::Contain: { case CSS::BackgroundSize::Contain: {
float max_width_ratio = (float)background_positioning_area.width() / (float)image.width(); float max_width_ratio = background_positioning_area.width() / image.width();
float max_height_ratio = (float)background_positioning_area.height() / (float)image.height(); float max_height_ratio = background_positioning_area.height() / image.height();
float ratio = min(max_width_ratio, max_height_ratio); float ratio = min(max_width_ratio, max_height_ratio);
image_rect.set_size(roundf(image.width() * ratio), roundf(image.height() * ratio)); image_rect.set_size(roundf(image.width() * ratio), roundf(image.height() * ratio));
break; break;
} }
case CSS::BackgroundSize::Cover: { case CSS::BackgroundSize::Cover: {
float max_width_ratio = (float)background_positioning_area.width() / (float)image.width(); float max_width_ratio = background_positioning_area.width() / image.width();
float max_height_ratio = (float)background_positioning_area.height() / (float)image.height(); float max_height_ratio = background_positioning_area.height() / image.height();
float ratio = max(max_width_ratio, max_height_ratio); float ratio = max(max_width_ratio, max_height_ratio);
image_rect.set_size(roundf(image.width() * ratio), roundf(image.height() * ratio)); image_rect.set_size(roundf(image.width() * ratio), roundf(image.height() * ratio));
break; break;
@ -176,7 +176,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
x_step = image_rect.width(); x_step = image_rect.width();
repeat_x = false; repeat_x = false;
} else { } else {
int space = background_positioning_area.width() % image_rect.width(); float space = fmodf(background_positioning_area.width(), image_rect.width());
x_step = image_rect.width() + ((float)space / (float)(whole_images - 1)); x_step = image_rect.width() + ((float)space / (float)(whole_images - 1));
repeat_x = true; repeat_x = true;
} }
@ -207,7 +207,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
y_step = image_rect.height(); y_step = image_rect.height();
repeat_y = false; repeat_y = false;
} else { } else {
int space = background_positioning_area.height() % image_rect.height(); float space = fmodf(background_positioning_area.height(), image_rect.height());
y_step = image_rect.height() + ((float)space / (float)(whole_images - 1)); y_step = image_rect.height() + ((float)space / (float)(whole_images - 1));
repeat_y = true; repeat_y = true;
} }

View File

@ -14,6 +14,6 @@
namespace Web::Painting { namespace Web::Painting {
void paint_background(PaintContext&, Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::IntRect const&, Color background_color, Vector<CSS::BackgroundLayerData> const*, BorderRadiusData const&); void paint_background(PaintContext&, Layout::NodeWithStyleAndBoxModelMetrics const&, Gfx::FloatRect const&, Color background_color, Vector<CSS::BackgroundLayerData> const*, BorderRadiusData const&);
} }

View File

@ -43,9 +43,7 @@ BorderRadiusData normalized_border_radius_data(Layout::Node const& node, Gfx::Fl
void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const& a_rect, BorderRadiusData const& border_radius_data, BordersData const& borders_data) void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const& a_rect, BorderRadiusData const& border_radius_data, BordersData const& borders_data)
{ {
// FIXME: This is a hack that snaps the incoming rect to integer pixel values before painting each side. auto rect = a_rect.to_rounded<float>();
// This needs a more general solution.
auto rect = enclosing_int_rect(a_rect).to_type<float>();
const auto& border_data = [&] { const auto& border_data = [&] {
switch (edge) { switch (edge) {

View File

@ -55,7 +55,7 @@ void InlinePaintable::paint(PaintContext& context, Painting::PaintPhase phase) c
} }
auto border_radius_data = Painting::normalized_border_radius_data(layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); auto border_radius_data = Painting::normalized_border_radius_data(layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius);
Painting::paint_background(context, layout_node(), enclosing_int_rect(absolute_fragment_rect), computed_values().background_color(), &computed_values().background_layers(), border_radius_data); Painting::paint_background(context, layout_node(), absolute_fragment_rect, computed_values().background_color(), &computed_values().background_layers(), border_radius_data);
if (auto computed_box_shadow = computed_values().box_shadow(); !computed_box_shadow.is_empty()) { if (auto computed_box_shadow = computed_values().box_shadow(); !computed_box_shadow.is_empty()) {
Vector<Painting::ShadowData> resolved_box_shadow_data; Vector<Painting::ShadowData> resolved_box_shadow_data;

View File

@ -184,13 +184,13 @@ void PaintableBox::paint_background(PaintContext& context) const
if (layout_box().is_body() && document().html_element()->should_use_body_background_properties()) if (layout_box().is_body() && document().html_element()->should_use_body_background_properties())
return; return;
Gfx::IntRect background_rect; Gfx::FloatRect background_rect;
Color background_color = computed_values().background_color(); Color background_color = computed_values().background_color();
auto* background_layers = &computed_values().background_layers(); auto* background_layers = &computed_values().background_layers();
if (layout_box().is_root_element()) { if (layout_box().is_root_element()) {
// CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas. // CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas.
background_rect = context.viewport_rect(); background_rect = context.viewport_rect().to_type<float>();
// Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent, // Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent,
// user agents must instead propagate the computed values of the background properties from that elements first HTML BODY child element. // user agents must instead propagate the computed values of the background properties from that elements first HTML BODY child element.
@ -199,13 +199,13 @@ void PaintableBox::paint_background(PaintContext& context) const
background_color = document().background_color(context.palette()); background_color = document().background_color(context.palette());
} }
} else { } else {
background_rect = enclosing_int_rect(absolute_padding_box_rect()); background_rect = absolute_padding_box_rect();
} }
// HACK: If the Box has a border, use the bordered_rect to paint the background. // HACK: If the Box has a border, use the bordered_rect to paint the background.
// This way if we have a border-radius there will be no gap between the filling and actual border. // This way if we have a border-radius there will be no gap between the filling and actual border.
if (computed_values().border_top().width || computed_values().border_right().width || computed_values().border_bottom().width || computed_values().border_left().width) if (computed_values().border_top().width || computed_values().border_right().width || computed_values().border_bottom().width || computed_values().border_left().width)
background_rect = enclosing_int_rect(absolute_border_box_rect()); background_rect = absolute_border_box_rect();
Painting::paint_background(context, layout_box(), background_rect, background_color, background_layers, normalized_border_radius_data()); Painting::paint_background(context, layout_box(), background_rect, background_color, background_layers, normalized_border_radius_data());
} }