LibWeb: Separate device pixel conversion helpers from PaintContext

In the upcoming change, device pixel conversion of ClipFrame will
happen during display list replay, where PaintContext is not available,
so let’s move it out of PaintContext.
This commit is contained in:
Aliaksandr Kalenik 2025-07-13 03:54:55 +02:00 committed by Jelle Raaijmakers
commit 7e333cdcf7
Notes: github-actions[bot] 2025-07-14 13:49:44 +00:00
12 changed files with 150 additions and 74 deletions

View file

@ -35,7 +35,7 @@ AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(i64, UniqueNodeID, Comparison, Increment, Ca
namespace Web::Painting {
class BackingStore;
class DevicePixelConverter;
class DisplayList;
class DisplayListPlayerSkia;
class DisplayListRecorder;

View file

@ -116,10 +116,10 @@ void paint_background(PaintContext& context, PaintableBox const& paintable_box,
display_list_recorder.fill_rect_with_rounded_corners(
context.rounded_device_rect(color_box.rect).to_type<int>(),
resolved_background.color,
color_box.radii.top_left.as_corner(context),
color_box.radii.top_right.as_corner(context),
color_box.radii.bottom_right.as_corner(context),
color_box.radii.bottom_left.as_corner(context));
color_box.radii.top_left.as_corner(context.device_pixel_converter()),
color_box.radii.top_right.as_corner(context.device_pixel_converter()),
color_box.radii.bottom_right.as_corner(context.device_pixel_converter()),
color_box.radii.bottom_left.as_corner(context.device_pixel_converter()));
}
struct {

View file

@ -10,11 +10,11 @@
namespace Web::Painting {
CornerRadius BorderRadiusData::as_corner(PaintContext const& context) const
CornerRadius BorderRadiusData::as_corner(DevicePixelConverter const& device_pixel_scale) const
{
return CornerRadius {
context.floored_device_pixels(horizontal_radius).value(),
context.floored_device_pixels(vertical_radius).value()
device_pixel_scale.floored_device_pixels(horizontal_radius).value(),
device_pixel_scale.floored_device_pixels(vertical_radius).value()
};
}

View file

@ -25,7 +25,7 @@ struct BorderRadiusData {
CSSPixels horizontal_radius { 0 };
CSSPixels vertical_radius { 0 };
CornerRadius as_corner(PaintContext const& context) const;
CornerRadius as_corner(DevicePixelConverter const& device_pixel_converter) const;
inline operator bool() const
{
@ -91,15 +91,15 @@ struct BorderRadiiData {
shrink(-top, -right, -bottom, -left);
}
inline CornerRadii as_corners(PaintContext const& context) const
inline CornerRadii as_corners(DevicePixelConverter const& device_pixel_converter) const
{
if (!has_any_radius())
return {};
return CornerRadii {
top_left.as_corner(context),
top_right.as_corner(context),
bottom_right.as_corner(context),
bottom_left.as_corner(context)
top_left.as_corner(device_pixel_converter),
top_right.as_corner(device_pixel_converter),
bottom_right.as_corner(device_pixel_converter),
bottom_left.as_corner(device_pixel_converter)
};
}
};

View file

@ -17,10 +17,10 @@ ScopedCornerRadiusClip::ScopedCornerRadiusClip(PaintContext& context, DevicePixe
if (!do_apply)
return;
CornerRadii const corner_radii {
.top_left = border_radii.top_left.as_corner(context),
.top_right = border_radii.top_right.as_corner(context),
.bottom_right = border_radii.bottom_right.as_corner(context),
.bottom_left = border_radii.bottom_left.as_corner(context)
.top_left = border_radii.top_left.as_corner(context.device_pixel_converter()),
.top_right = border_radii.top_right.as_corner(context.device_pixel_converter()),
.bottom_right = border_radii.bottom_right.as_corner(context.device_pixel_converter()),
.bottom_left = border_radii.bottom_left.as_corner(context.device_pixel_converter())
};
m_has_radius = corner_radii.has_any_radius();
if (!m_has_radius)

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
namespace Web::Painting {
class DevicePixelConverter {
public:
DevicePixels rounded_device_pixels(CSSPixels css_pixels) const
{
return round(css_pixels.to_double() * m_device_pixels_per_css_pixel);
}
DevicePixels enclosing_device_pixels(CSSPixels css_pixels) const
{
return ceil(css_pixels.to_double() * m_device_pixels_per_css_pixel);
}
DevicePixels floored_device_pixels(CSSPixels css_pixels) const
{
return floor(css_pixels.to_double() * m_device_pixels_per_css_pixel);
}
DevicePixelPoint rounded_device_point(CSSPixelPoint point) const
{
return {
round(point.x().to_double() * m_device_pixels_per_css_pixel),
round(point.y().to_double() * m_device_pixels_per_css_pixel)
};
}
DevicePixelPoint floored_device_point(CSSPixelPoint point) const
{
return {
floor(point.x().to_double() * m_device_pixels_per_css_pixel),
floor(point.y().to_double() * m_device_pixels_per_css_pixel)
};
}
DevicePixelRect enclosing_device_rect(CSSPixelRect rect) const
{
return {
floor(rect.x().to_double() * m_device_pixels_per_css_pixel),
floor(rect.y().to_double() * m_device_pixels_per_css_pixel),
ceil(rect.width().to_double() * m_device_pixels_per_css_pixel),
ceil(rect.height().to_double() * m_device_pixels_per_css_pixel)
};
}
DevicePixelRect rounded_device_rect(CSSPixelRect rect) const
{
return {
round(rect.x().to_double() * m_device_pixels_per_css_pixel),
round(rect.y().to_double() * m_device_pixels_per_css_pixel),
round(rect.width().to_double() * m_device_pixels_per_css_pixel),
round(rect.height().to_double() * m_device_pixels_per_css_pixel)
};
}
DevicePixelSize enclosing_device_size(CSSPixelSize size) const
{
return {
ceil(size.width().to_double() * m_device_pixels_per_css_pixel),
ceil(size.height().to_double() * m_device_pixels_per_css_pixel)
};
}
DevicePixelSize rounded_device_size(CSSPixelSize size) const
{
return {
round(size.width().to_double() * m_device_pixels_per_css_pixel),
round(size.height().to_double() * m_device_pixels_per_css_pixel)
};
}
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
DevicePixelConverter(double device_pixels_per_css_pixel)
: m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
{
}
private:
double m_device_pixels_per_css_pixel { 0 };
};
}

View file

@ -62,7 +62,7 @@ void FieldSetPaintable::paint(PaintContext& context, PaintPhase phase) const
.left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
};
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context.device_pixel_converter()), borders_data.to_device_pixels(context));
auto top_border_data = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top();
auto top_border = context.enclosing_device_pixels(top_border_data.width).value();
@ -92,7 +92,7 @@ void FieldSetPaintable::paint(PaintContext& context, PaintPhase phase) const
display_list_recorder.save();
display_list_recorder.add_clip_rect(left_segment.to_type<int>());
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), top_border_only.to_device_pixels(context));
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context.device_pixel_converter()), top_border_only.to_device_pixels(context));
display_list_recorder.restore();
display_list_recorder.save();
@ -100,7 +100,7 @@ void FieldSetPaintable::paint(PaintContext& context, PaintPhase phase) const
paint_all_borders(
display_list_recorder,
fieldset_border_rect,
normalized_border_radii_data().as_corners(context),
normalized_border_radii_data().as_corners(context.device_pixel_converter()),
top_border_only.to_device_pixels(context));
display_list_recorder.restore();
}

View file

@ -14,7 +14,7 @@ static u64 s_next_paint_generation_id = 0;
PaintContext::PaintContext(Painting::DisplayListRecorder& display_list_recorder, Palette const& palette, double device_pixels_per_css_pixel)
: m_display_list_recorder(display_list_recorder)
, m_palette(palette)
, m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
, m_device_pixel_converter(device_pixels_per_css_pixel)
, m_paint_generation_id(s_next_paint_generation_id++)
{
}
@ -22,83 +22,61 @@ PaintContext::PaintContext(Painting::DisplayListRecorder& display_list_recorder,
CSSPixelRect PaintContext::css_viewport_rect() const
{
return {
m_device_viewport_rect.x().value() / m_device_pixels_per_css_pixel,
m_device_viewport_rect.y().value() / m_device_pixels_per_css_pixel,
m_device_viewport_rect.width().value() / m_device_pixels_per_css_pixel,
m_device_viewport_rect.height().value() / m_device_pixels_per_css_pixel
m_device_viewport_rect.x().value() / m_device_pixel_converter.device_pixels_per_css_pixel(),
m_device_viewport_rect.y().value() / m_device_pixel_converter.device_pixels_per_css_pixel(),
m_device_viewport_rect.width().value() / m_device_pixel_converter.device_pixels_per_css_pixel(),
m_device_viewport_rect.height().value() / m_device_pixel_converter.device_pixels_per_css_pixel()
};
}
DevicePixels PaintContext::rounded_device_pixels(CSSPixels css_pixels) const
{
return round(css_pixels.to_double() * m_device_pixels_per_css_pixel);
return m_device_pixel_converter.rounded_device_pixels(css_pixels);
}
DevicePixels PaintContext::enclosing_device_pixels(CSSPixels css_pixels) const
{
return ceil(css_pixels.to_double() * m_device_pixels_per_css_pixel);
return m_device_pixel_converter.enclosing_device_pixels(css_pixels);
}
DevicePixels PaintContext::floored_device_pixels(CSSPixels css_pixels) const
{
return floor(css_pixels.to_double() * m_device_pixels_per_css_pixel);
return m_device_pixel_converter.floored_device_pixels(css_pixels);
}
DevicePixelPoint PaintContext::rounded_device_point(CSSPixelPoint point) const
{
return {
round(point.x().to_double() * m_device_pixels_per_css_pixel),
round(point.y().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.rounded_device_point(point);
}
DevicePixelPoint PaintContext::floored_device_point(CSSPixelPoint point) const
{
return {
floor(point.x().to_double() * m_device_pixels_per_css_pixel),
floor(point.y().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.floored_device_point(point);
}
DevicePixelRect PaintContext::enclosing_device_rect(CSSPixelRect rect) const
{
return {
floor(rect.x().to_double() * m_device_pixels_per_css_pixel),
floor(rect.y().to_double() * m_device_pixels_per_css_pixel),
ceil(rect.width().to_double() * m_device_pixels_per_css_pixel),
ceil(rect.height().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.enclosing_device_rect(rect);
}
DevicePixelRect PaintContext::rounded_device_rect(CSSPixelRect rect) const
{
return {
round(rect.x().to_double() * m_device_pixels_per_css_pixel),
round(rect.y().to_double() * m_device_pixels_per_css_pixel),
round(rect.width().to_double() * m_device_pixels_per_css_pixel),
round(rect.height().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.rounded_device_rect(rect);
}
DevicePixelSize PaintContext::enclosing_device_size(CSSPixelSize size) const
{
return {
ceil(size.width().to_double() * m_device_pixels_per_css_pixel),
ceil(size.height().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.enclosing_device_size(size);
}
DevicePixelSize PaintContext::rounded_device_size(CSSPixelSize size) const
{
return {
round(size.width().to_double() * m_device_pixels_per_css_pixel),
round(size.height().to_double() * m_device_pixels_per_css_pixel)
};
return m_device_pixel_converter.rounded_device_size(size);
}
CSSPixels PaintContext::scale_to_css_pixels(DevicePixels device_pixels) const
{
return CSSPixels::nearest_value_for(device_pixels.value() / m_device_pixels_per_css_pixel);
return CSSPixels::nearest_value_for(device_pixels.value() / m_device_pixel_converter.device_pixels_per_css_pixel());
}
CSSPixelPoint PaintContext::scale_to_css_point(DevicePixelPoint point) const

View file

@ -11,6 +11,7 @@
#include <LibGfx/Palette.h>
#include <LibGfx/Rect.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/DevicePixelConverter.h>
#include <LibWeb/PixelUnits.h>
namespace Web {
@ -68,21 +69,22 @@ public:
PaintContext clone(Painting::DisplayListRecorder& painter) const
{
auto clone = PaintContext(painter, m_palette, m_device_pixels_per_css_pixel);
auto clone = PaintContext(painter, m_palette, m_device_pixel_converter.device_pixels_per_css_pixel());
clone.m_device_viewport_rect = m_device_viewport_rect;
clone.m_should_show_line_box_borders = m_should_show_line_box_borders;
clone.m_should_paint_overlay = m_should_paint_overlay;
return clone;
}
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
Painting::DevicePixelConverter const& device_pixel_converter() const { return m_device_pixel_converter; }
double device_pixels_per_css_pixel() const { return m_device_pixel_converter.device_pixels_per_css_pixel(); }
u64 paint_generation_id() const { return m_paint_generation_id; }
private:
Painting::DisplayListRecorder& m_display_list_recorder;
Palette m_palette;
double m_device_pixels_per_css_pixel { 0 };
Painting::DevicePixelConverter m_device_pixel_converter;
DevicePixelRect m_device_viewport_rect;
bool m_should_show_line_box_borders { false };
bool m_should_paint_overlay { true };

View file

@ -479,7 +479,7 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
border_radius_data.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
borders_rect.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context), outline_data->to_device_pixels(context));
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context.device_pixel_converter()), outline_data->to_device_pixels(context));
}
}
@ -571,7 +571,7 @@ void PaintableBox::paint_border(PaintContext& context) const
.bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
.left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
};
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context.device_pixel_converter()), borders_data.to_device_pixels(context));
}
void PaintableBox::paint_backdrop_filter(PaintContext& context) const
@ -661,7 +661,7 @@ void PaintableBox::apply_clip(PaintContext& context, RefPtr<ClipFrame const> con
clip_scroll_frame_id = clip_rect.enclosing_scroll_frame->id();
display_list_recorder.push_scroll_frame_id(clip_scroll_frame_id);
auto rect = context.rounded_device_rect(clip_rect.rect).to_type<int>();
auto corner_radii = clip_rect.corner_radii.as_corners(context);
auto corner_radii = clip_rect.corner_radii.as_corners(context.device_pixel_converter());
if (corner_radii.has_any_radius()) {
display_list_recorder.add_rounded_rect_clip(corner_radii, rect, CornerClip::Outside);
} else {

View file

@ -41,10 +41,10 @@ void paint_box_shadow(PaintContext& context,
.color = box_shadow_data.color,
.placement = box_shadow_data.placement,
.corner_radii = CornerRadii {
.top_left = border_radii.top_left.as_corner(context),
.top_right = border_radii.top_right.as_corner(context),
.bottom_right = border_radii.bottom_right.as_corner(context),
.bottom_left = border_radii.bottom_left.as_corner(context) },
.top_left = border_radii.top_left.as_corner(context.device_pixel_converter()),
.top_right = border_radii.top_right.as_corner(context.device_pixel_converter()),
.bottom_right = border_radii.bottom_right.as_corner(context.device_pixel_converter()),
.bottom_left = border_radii.bottom_left.as_corner(context.device_pixel_converter()) },
.offset_x = offset_x.value(),
.offset_y = offset_y.value(),
.blur_radius = blur_radius.value(),

View file

@ -354,7 +354,7 @@ static void paint_separate_cell_borders(PaintableBox const& cell_box, HashMap<Ce
.left = cell_box.box_model().border.left == 0 ? CSS::BorderData() : cell_box.computed_values().border_left(),
};
auto cell_rect = cell_coordinates_to_device_rect.get({ cell_box.table_cell_coordinates()->row_index, cell_box.table_cell_coordinates()->column_index }).value();
paint_all_borders(context.display_list_recorder(), cell_rect, cell_box.normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
paint_all_borders(context.display_list_recorder(), cell_rect, cell_box.normalized_border_radii_data().as_corners(context.device_pixel_converter()), borders_data.to_device_pixels(context));
}
void paint_table_borders(PaintContext& context, PaintableBox const& table_paintable)
@ -428,10 +428,10 @@ void paint_table_borders(PaintContext& context, PaintableBox const& table_painta
for (auto const& cell_box : cell_boxes) {
auto const& border_radii_data = cell_box.normalized_border_radii_data();
auto top_left = border_radii_data.top_left.as_corner(context);
auto top_right = border_radii_data.top_right.as_corner(context);
auto bottom_right = border_radii_data.bottom_right.as_corner(context);
auto bottom_left = border_radii_data.bottom_left.as_corner(context);
auto top_left = border_radii_data.top_left.as_corner(context.device_pixel_converter());
auto top_right = border_radii_data.top_right.as_corner(context.device_pixel_converter());
auto bottom_right = border_radii_data.bottom_right.as_corner(context.device_pixel_converter());
auto bottom_left = border_radii_data.bottom_left.as_corner(context.device_pixel_converter());
if (!top_left && !top_right && !bottom_left && !bottom_right) {
continue;
} else {
@ -441,7 +441,7 @@ void paint_table_borders(PaintContext& context, PaintableBox const& table_painta
.bottom = cell_box.box_model().border.bottom == 0 ? CSS::BorderData() : cell_box.computed_values().border_bottom(),
.left = cell_box.box_model().border.left == 0 ? CSS::BorderData() : cell_box.computed_values().border_left(),
};
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(cell_box.absolute_border_box_rect()), cell_box.normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(cell_box.absolute_border_box_rect()), cell_box.normalized_border_radii_data().as_corners(context.device_pixel_converter()), borders_data.to_device_pixels(context));
}
}
}